Reputation: 2411
I have a pytest.fixture
that has one positional arg and one keyword arg.
Per Pass a parameter to a fixture function, one can pass args to a fixture using pytest.mark.parametrize
with the indirect
arg set to the fixture's name.
Please see the below sample code.
import pytest
class Foo:
def __init__(self, a: str, b: str):
self.a = a
self.b = b
@pytest.fixture
def a() -> str:
return "alphabet"
@pytest.fixture
def foo_obj(a: str, b: str = "bar") -> Foo:
return Foo(a, b)
@pytest.mark.parametrize("foo_obj", [("applesauce", "baz")], indirect=["foo_obj"])
def test_thing(foo_obj) -> None:
assert foo_obj.a == "applesauce"
assert foo_obj.b == "baz"
This test fails currently: the "applesauce"
and "baz"
aren't getting passed into the fixture foo_obj
.
My questions:
foo_obj
?b
in the pytest.mark.parametrize
decorator call?
Versions
Python==3.8.5
pytest==6.0.1
Upvotes: 1
Views: 2400
Reputation: 2411
The answer from @MrBeanBremen put me on the scent of this answer, which talks about implicit indirect parametrization.
@pytest.fixture
def foo_obj(a: str, b: str) -> Foo: # Note: b was changed to positional arg
print("hi")
return Foo(a, b)
@pytest.mark.parametrize("a, b", [("applesauce", "baz")])
def test_thing(foo_obj) -> None:
assert foo_obj.a == "applesauce"
assert foo_obj.b == "baz"
The tests pass in the above case.
It seems this implicit indirect parametrization can be used for positional args, but not for keyword args. For example, if b
was left as a keyword argument in the fixture foo_obj
, pytest fails with the message: In test_thing: function uses no argument 'b'
.
I am left wondering, is it possible to have keyword args be parameterized in this manner?
Trying a 2nd time to use keyword arg
I decided to make a new attempt again pulling on the answer from @MrBeanBremen and also this answer: Provide default argument value for py.test fixture function
@pytest.fixture
def a() -> str:
return "alphabet"
@pytest.fixture
def foo_obj(request, a) -> Foo:
return Foo(request.param.get("a", a), request.param.get("b", "bar"))
@pytest.mark.parametrize("foo_obj", [dict(a="applesauce", b="baz")], indirect=True)
def test_thing(foo_obj) -> None:
# Override both defaults, passes
assert foo_obj.a == "applesauce"
assert foo_obj.b == "baz"
@pytest.mark.parametrize("foo_obj", [dict(b="baz")], indirect=True)
def test_thing_only_b(foo_obj) -> None:
# Use the default for a
assert foo_obj.a == "alphabet"
assert foo_obj.b == "baz"
This works! I feel as if it's a little convoluted with the foo_obj
fixture accepting two args, when it sometimes uses one or the other.
Upvotes: 5
Reputation: 16815
I think you are mixing two things here: passing parameters to a fixture via request.params
, and basing a fixture an another fixture (or fixtures).
To use the parameters used in mark.parametrize
in your fixture, you have to take them from request.params
, as shown in your linked question:
@pytest.fixture
def foo_obj(request) -> Foo:
return Foo(request.param[0], request.param[1])
What you are doing instead, is basing foo_obj
on the fixture a
, meaning, that a
is the result of that fixture (e.g. it is always "a"
), and a constant parameter b
. Both have nothing to do with the values set by parametrize
- these are set in request.params
as shown above.
Upvotes: 3