pytest using fixtures as arguments in parametrize

85.8k Views Asked by At

I would like to use fixtures as arguments of pytest.mark.parametrize or something that would have the same results.

For example:

import pytest
import my_package
    
@pytest.fixture
def dir1_fixture():
    return '/dir1'
    
@pytest.fixture
def dir2_fixture():
    return '/dir2'

@pytest.parametrize('dirname, expected', [(dir1_fixture, 'expected1'), (dir2_fixture, 'expected2')])
def test_directory_command(dirname, expected):
    result = my_package.directory_command(dirname)
    assert result == expected

The problem with fixture params is that every param of the fixture will get run every time it's used, but I don't want that. I want to be able to choose which fixtures will get used depending on the test.

9

There are 9 best solutions below

6
On BEST ANSWER

Will was on the right path, you should use request.getfixturevalue to retrieve the fixture.

But you can do it right in the test, which is simpler.

@pytest.mark.parametrize('dirname, expected', [
    ('dir1_fixture', 'expected1'),
    ('dir2_fixture', 'expected2')])
def test_directory_command(dirname, expected, request):
    result = my_package.directory_command(request.getfixturevalue(dirname))
    assert result == expected

Another way is to use lazy-fixture plugin:

@pytest.mark.parametrize('dirname, expected', [
    (pytest.lazy_fixture('dir1_fixture'), 'expected1'),
    (pytest.lazy_fixture('dir2_fixture'), 'expected2')])
def test_directory_command(dirname, expected):
    result = my_package.directory_command(dirname)
    assert result == expected
0
On

An addition to the accepted answer for async tests: lazy_fixture also work on async tests, where as the getfixturevalue-method doesn't, as documented in an open issue in pytest-asyncio:

@pytest.fixture
async def a():
    return 1
@pytest.fixture
async def b():
    return 2
@pytest.mark.asyncio
@pytest.mark.parametrize('i', [pytest.lazy_fixture('a'), pytest.lazy_fixture('b')])
async def test_this(i):
    assert i in [1, 2]
1
On

DO NOT TRY TO CHANGE FIXTURE PARAMETERS DURING TEST EXECUTION

Invalid example: @pytest.fixture(scope="class", params=other_fixture)

Now I'll explain why it doesn't work:

  1. Pytest creates session objects before running the test, containing the parameters with which the test will run. During the execution of the test; you cannot change the parameters

  2. If you really want to do this (change the parameters dynamically), you can use an intermediate text file: "params.txt". Example: @pytest.fixture(scope="class", params=json.load(open("topics.txt"))). Again, you will not be able to change the content of the file during the test; because if you change it; will not be visible in the test. To do this; we need to change the contents of the file when the program starts and before the session objects are created. To do that; define a method pytest_sessionstart(session) in conftest.py where you change the file content.

  3. For more details; check this documentation: How to run a method before all tests in all classes? and https://docs.pytest.org/en/6.2.x/reference.html#pytest.hookspec.pytest_sessionstart

0
On

As for now, my only solution is to create a fixture that returns a dictionary of fixtures.

import pytest
import my_package

@pytest.fixture
def dir1_fixture():
    return '/dir1'

@pytest.fixture
def dir2_fixture():
    return '/dir2'

@pytest.fixture
def dir_fixtures(
    dir1_fixture,
    dir2_fixture
    ):
    return {
        'dir1_fixture': dir1_fixture,
        'dir2_fixture': dir2_fixture
    }

@pytest.mark.parametrize('fixture_name, expected', [('dir1_fixture', 'expected1'), ('dir2_fixture', 'expected2')])
def test_directory_command(dir_fixtures, fixture_name, expected):
    dirname = dir_fixtures[fixture_name]
    result = my_package.directory_command(dirname)
    assert result == expected

Not the best since it does not use a solution built into pytest, but it works for me.

3
On

If you're on pytest 3.0 or later, I think you should be able to solve this particular scenario by writing a fixture using getfixturevalue:

@pytest.fixture(params=['dir1_fixture', 'dir2_fixture'])
def dirname(request):
    return request.getfixturevalue(request.param)

However, you can't use this approach if the fixture you're attempting to dynamically load is parametrized.

Alternatively, you might be able to figure something out with the pytest_generate_tests hook. I haven't been able to bring myself to look into that much, though.

0
On

This might be off topic but I came here trying to figure out how to read a whole dir. of test data files and run them, one by one as a fixture. So if you had all the dirs in a folder say test/examples you could do something like:

import pytest
from pathlib import Path

@pytest.fixture(
    # I guess this is anything that generates a list
    # Make it a list of tuples if you want results as well, or JSON?
    params=[str(p) for p in Path("test/examples").glob("*") if p.is_dir()]
)
def dirname(request):
    return request.param

def test_directory_command(dirname):  # dirname will be enumerated
    result = my_package.directory_command(dirname)
    assert ...

Check the docs

1
On

This isn't currently supported by pytest. There is an open feature request for it though (which has been opened in 2013).

0
On

For example, with request.getfixturevalue(), you can call fruits() and meat() fixtures by the names set in @pytest.mark.parametrize() as shown below:

import pytest

@pytest.fixture
def fruits():
    return "orange" 

@pytest.fixture
def meat():
    return "beef"

@pytest.mark.parametrize(
    ("food", "expected"),
    (
        ("fruits", "orange"),
        ("meat", "beef")
    )
)
def test(request, food, expected):
    result = request.getfixturevalue(food) # Here
    print(result, expected)
    assert result == expected

Then, request.getfixturevalue() could call fruits() and meat() fixtures by the names set in @pytest.mark.parametrize() as shown below:

$ pytest -q -rP
..                               [100%]
=============== PASSES ================ 
_________ test[fruits-orange] _________ 
-------- Captured stdout call --------- 
orange orange
___________ test[meat-beef] ___________ 
-------- Captured stdout call --------- 
beef beef
2 passed in 0.10s
1
On

You can use one parametrised fixture instead of 2 different and then use indirect.

import pytest
import my_package

@pytest.fixture
def dirname(request):
    return request.param
   
    
@pytest.mark.parametrize(
    'dirname, expected', 
    [('/dir1', 'expected1'), ('/dir2', 'expected2')], 
    indirect=['dirname']
)
def test_directory_command(dirname, expected):
    result = my_package.directory_command(dirname)
    assert result == expected

In this case you parametrise you fixture using request (thanks to indirect parameter), and expected is just a simple param