Search code examples
pythonpytestparametrized-testing

pytest indirect parametrization fails when mixing it with non-fixture parameters


First of all, sorry for the title, I couldn't think of something better.

The code below is fictitious because the real code where this is happening is much longer and this little example shows the same problem.

I have this code in my conftest.py:

@pytest.fixture()
def my_fixture(argument):
    return f'expected{argument[-1]}'

It works as expected when parametrizing only the fixture in a test.

But if I have this test function:

@pytest.mark.parametrize('my_fixture, expected', [
    ('fixture_param1', 'expected1'),
    ('fixture_param2', 'expected2')
], indirect=['my_fixture'])
def test_indirect(my_fixture, expected):
    value = my_fixture
    assert value == expected

This fails with the following error:

  @pytest.fixture()
  def my_fixture(argument):
E       fixture 'argument' not found
>       available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, cov, doctest_namespace, log_paths, monkeypatch, my_fixture, no_cover, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, unreadable_file
>       use 'pytest --fixtures [testpath]' for help on them.

Obviously this works if the test function is called like this:

@pytest.mark.parametrize('argument, expected', [
    ('fixture_param1', 'expected1'),
    ('fixture_param2', 'expected2')
], indirect=['my_fixture'])
def test_indirect(my_fixture, expected):
    value = my_fixture
    assert value == expected

but I find this syntax confusing because I would like to have my_fixture explicitly in the parametrization rather than the argument name.

What am I doing wrong? Apparently, what I try to do is correct, one can use both a fixture and a normal parameter in a parametrize call as long as the indirect parametrizations are declared. I'm at a loss here…

Thanks in advance!


Solution

  • Well, turns out the solution is quite simple, and as far as I know, quite undocumented. I mean, the mechanism is perfectly documented, but the fact that is the only way of achieving what I needed is not.

    Using this:

    # Before it was 'argument' instead of 'request.param'
    @pytest.fixture()
    def my_fixture(request):
        return f'expected{request.param[-1]}'
    

    makes everything work using the code which failed for me at first:

    # Now, it works!
    @pytest.mark.parametrize('my_fixture, expected', [
        ('fixture_param1', 'expected1'),
        ('fixture_param2', 'expected2')
    ], indirect=['my_fixture'])
    def test_indirect(my_fixture, expected):
        value = my_fixture
        assert value == expected
    

    Turns out that using request.param solved the problem because the original code made pytest think that argument was a fixture (don't ask me why), and of course request IS a fixture, so problem solved. Weren't for the fact that request fixture allows accesing parametrized params, this would not work.

    If I find documentation related to this (I found the solution by sheer accident after endless hours investigating on the net) I'll post here in this reply for completeness.

    Sorry for the noise!