# How to Use Fixtures as Arguments in pytest.mark.parametrize

## TL;DR

Time is a precious resource so I won't waste yours. In this post, you'll learn how to use a pytest fixture in parametrize using a library or getfixturevalue.

## Introduction

In this post, we'll see how we can use `pytest.mark.parametrize` with fixtures. This is a [long-wanted feature](https://github.com/pytest-dev/pytest/issues/349) that dates back to 2013. Even though `pytest` doesn't support it yet, you'll see that we can actually make it happen.

## Problem

### You want to pass a fixture to parametrize.

Suppose that you have a simple function called `is_even(n)` that returns true if `n` is divisible by 2. Then you create a simple test for it that receives a fixture named `two` that returns 2. To make the test more robust, you set up another fixture named `four` that returns 4. Now you have two individual tests, as illustrated below.

Implementation:
```python
def is_even(n: int) -> bool:
    """Returns True if n is even."""
    return n % 2 == 0
```
Tests:
```python
@pytest.fixture()
def two():
    return 2

@pytest.fixture()
def four():
    return 4

def test_four_is_even(four):
    """Asserts that four is even"""
    assert is_even(four)

def test_two_is_even(two):
    """Asserts that two is even"""
    assert is_even(two)
```

If we run these tests, they pass, which is good. Even though you’re quite happy with the outcome, you need to test one more thing. You want to assert that  [the multiplication of an even number by and odd one produces an even result](https://proofwiki.org/wiki/Odd_Number_multiplied_by_Even_Number_is_Even). To accomplish that, you create two more fixtures, `one` and `three`. You plan to use them as arguments in a parameterized test, like so:

```python
@pytest.fixture()
def one():
    return 1

@pytest.fixture()
def three():
    return 3

@pytest.mark.parametrize(
    "a, b",
    [
        (one, four),
        (two, three),
    ],
)
def test_multiply_is_even(a, b):
    """Assert that an odd number times even is even."""
    assert is_even(a * b)
```

When we run this test, we get the following output:

```console
_______________________ test_multiply_is_even[two-three] _______________________

a = <function two at 0x7f9d862ee790>, b = <function three at 0x7f9d862eedc0>

    @pytest.mark.parametrize(
        "a, b",
        [
            (one, four),
            (two, three),
        ],
    )
    def test_multiply_is_even(a, b):
        """Assert that an odd number times even is even."""
>       assert is_even(a * b)
E       TypeError: unsupported operand type(s) for *: 'function' and 'function'

tests/test_variables.py:71: TypeError
=========================== short test summary info ============================
FAILED tests/test_variables.py::test_multiply_is_even[one-four] - TypeError: ...
FAILED tests/test_variables.py::test_multiply_is_even[two-three] - TypeError:...
============================== 2 failed in 0.05s ===============================
```

As you can see, passing a fixture as argument in a parameterized test doesn't work. 

## Solution

To make that possible, we have two alternatives. The first one is using `request.getfixturevalue`, which is available on `pytest`. This function dynamically runs a named fixture function.

```python
@pytest.mark.parametrize(
    "a, b",
    [
        ("one", "four"),
        ("two", "three"),
    ],
)
def test_multiply_is_even_request(a, b, request):
    """Assert that an odd number times even is even."""
    a = request.getfixturevalue(a)
    b = request.getfixturevalue(b)
    assert is_even(a * b)
```

If we run the test again we get the following:

```console
============================= test session starts ==============================
...
collecting ... collected 2 items

tests/test_variables.py::test_multiply_is_even_request[one-four] PASSED  [ 50%]
tests/test_variables.py::test_multiply_is_even_request[two-three] PASSED [100%]

============================== 2 passed in 0.02s ===============================

Process finished with exit code 0
```

Great! It works like a charm. However, there’s one more alternative, and for that we’ll need a third-party package called [`pytest-lazy-fixture`](https://github.com/tvorog/pytest-lazy-fixture). Let’s see how the test looks like using this lib.

```python
@pytest.mark.parametrize(
    "a, b",
    [
        (pytest.lazy_fixture(("one", "four"))),
        # same as (pytest.lazy_fixture(("two", "three")))
        (pytest.lazy_fixture("two"), pytest.lazy_fixture("three")), 
    ],
)
def test_multiply(a, b):
    """Assert that an odd number times even is even."""
    assert is_even(a * b)
```
In this example, we use it by passing a tuple with the fixtures names or passing each one of them as a different argument. When we run this test, we can see it passes!

```console
============================= test session starts ==============================
...
collecting ... collected 2 items

tests/test_variables.py::test_multiply[one-four] PASSED                  [ 50%]
tests/test_variables.py::test_multiply[two-three] PASSED                 [100%]

============================== 2 passed in 0.02s ===============================

Process finished with exit code 0```

## Conclusion

That’s it for today, folks! I hope you’ve learned something different and useful. Being able to reuse fixtures in parametrized tests is a must when we want to avoid repetition. Unfortunately, `pytest` doesn’t support that yet. On the other hand, we can make it happen either by using `getfixturevalue` in `pytest` or through a third-party library. 


Other posts you may like:

- [Learn how to unit test REST APIs in Python with Pytest by example.](https://miguendes.me/3-ways-to-test-api-client-applications-in-python)

- [7 pytest Features and Plugins That Will Save You Tons of Time](https://miguendes.me/7-pytest-features-and-plugins-that-will-save-you-tons-of-time)

- [How to Use Fixtures as Arguments in pytest.mark.parametrize](https://miguendes.me/how-to-use-fixtures-as-arguments-in-pytestmarkparametrize)

- [How to Check if an Exception Is Raised (or Not) With pytest](https://miguendes.me/how-to-check-if-an-exception-is-raised-or-not-with-pytest)

- [7 pytest Plugins You Must Definitely Use](https://miguendes.me/7-pytest-plugins-you-must-definitely-use)

- [How to Disable Autouse Fixtures in pytest](https://miguendes.me/pytest-disable-autouse)

- [How to Use datetime.timedelta in Python With Examples](https://miguendes.me/how-to-use-datetimetimedelta-in-python-with-examples)

- [73 Examples to Help You Master Python's f-strings](https://miguendes.me/73-examples-to-help-you-master-pythons-f-strings)

- [The Best Way to Compare Two Dictionaries in Python](https://miguendes.me/the-best-way-to-compare-two-dictionaries-in-python)


See you next time!

This post was originally published at [https://miguendes.me](https://miguendes.me/how-to-use-fixtures-as-arguments-in-pytestmarkparametrize)
