How to Use Fixtures as Arguments in pytest.mark.parametrize
In this post, we'll see how we can use fixtures as arguments of
pytest.mark.parametrize. This is a long-wanted feature that dates back to 2013. Even though
pytest doesn't support it yet, you'll see that we can actually make it happen.
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.
def is_even(n: int) -> bool: """Returns True if n is even.""" return n % 2 == 0
def two(): return 2 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. To accomplish that, you create two more fixtures,
three. You plan to use them as arguments in a parameterized test, like so:
def one(): return 1 def three(): return 3 "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:
_______________________ 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.
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.
"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:
============================= 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. Let’s see how the test looks like using this lib.
"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!
============================= 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
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
pytest or through a third-party library.
If you liked this post, consider sharing it with your friends or buying me a coffee:)
Other posts you may like:
- How to Use datetime.timedelta in Python With Examples
- 73 Examples to Help You Master Python's f-strings
- How to Check if an Exception Is Raised (or Not) With pytest
- 3 Ways to Test API Client Applications in Python
- Everything You Need to Know About Python's Namedtuples
- The Best Way to Compare Two Dictionaries in Python
- 5 Hidden Python Features You Probably Never Heard Of
See you next time!
Hello! And what could you suggest, if I need to use in parametrization a fixture, which returns unpredictable number of values? Of course, we could use it in parametrization as a regular Python function, but what if it's impossible? For example, this question is discussed here, but no solution was found: github.com/pytest-dev/pytest/issues/349
In my case, I definitely need a fixture and not a regular function, because parametrized value depends on HTTP service response.
Here is example code which matches my case: pastebin.com/gLn1CrAa
Of course, it doesn't work, because I'm trying to use fixture directly in mark.parametrize. Maybe, there is some suitable solution?