Search code examples
pythonpytestfixtures

pytest: passing keyword arg to fixture using pytest.mark.parametrize with indirect parameterization


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:

  1. What am I doing wrong in passing the args to the fixture foo_obj?
  2. Is it possible to only enter the kwarg b in the pytest.mark.parametrize decorator call?

Versions

Python==3.8.5
pytest==6.0.1

Solution

  • 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.