How to Use Fixtures as Arguments in pytest.mark.parametrize
Learn how to use pytest mark parametrize with fixtures
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 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:
def is_even(n: int) -> bool:
"""Returns True if n is even."""
return n % 2 == 0
Tests:
@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. To accomplish that, you create two more fixtures, one
and three
. You plan to use them as arguments in a parameterized test, like so:
@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:
_______________________ 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.
@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:
============================= 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.
@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!
============================= 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.
7 pytest Features and Plugins That Will Save You Tons of Time
See you next time!
This post was originally published at https://miguendes.me