135

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.

Neuron
  • 5,141
  • 5
  • 38
  • 59
elveatles
  • 2,160
  • 3
  • 18
  • 16
  • 8
    [Issue #349 in the pytest repo](https://github.com/pytest-dev/pytest/issues/349) calls for what you want, it's not resolved as of August 2020 – jrieke Aug 05 '20 at 01:07
  • 2
    Nice read: https://miguendes.me/how-to-use-fixtures-as-arguments-in-pytestmarkparametrize – Michele Jul 06 '22 at 08:49

7 Answers7

77

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
Gino Mempin
  • 25,369
  • 29
  • 96
  • 135
ggguser
  • 1,872
  • 12
  • 9
  • 2
    The first one didn't work for me, the second did. – z33k Nov 18 '21 at 09:18
  • 21
    New pytester here-- two points that were not immediately obvious to me: (1) `request` is a built-in fixture of `pytest`, (2) the fixture name in `@pytest.mark.parameterize` should be passed as a string. Passing a pointer to the function as a test parameter will raise an exception like "fixture '' not found" – PetMetz Jun 29 '22 at 15:53
  • I could only get the first option to work by making the parameter a different name than the fixture itself. – Malachi Bazar Jul 20 '22 at 11:27
  • I'm using pytest 7 and had to manually include the fixture via `@pytest.mark.usefixture("fixture1", "fixture2")`, otherwise they won't be included in the request object. – killjoy Jul 31 '23 at 19:38
52

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.

Neuron
  • 5,141
  • 5
  • 38
  • 59
Will
  • 928
  • 11
  • 10
  • Thank you for your answer, but this isn't exactly what I'm looking for. From what I understand, the dirname fixture will run every fixture one at a time. What I'm looking for is a way to select a fixture from a test based on parameters. – elveatles Mar 10 '17 at 02:27
  • 2
    I'm not quite sure I follow what you're looking for. Nevertheless, if you have some sort of logic you would prefer to perform in your test case that determines the setup you need, you are free to dynamically load the fixture via request.getfixturevalue() from there, instead. – Will Mar 13 '17 at 00:42
15

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

Neuron
  • 5,141
  • 5
  • 38
  • 59
Robie Basak
  • 6,492
  • 2
  • 30
  • 34
13

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.

Neuron
  • 5,141
  • 5
  • 38
  • 59
elveatles
  • 2,160
  • 3
  • 18
  • 16
4

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

0

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

CpILL
  • 6,169
  • 5
  • 38
  • 37
-1

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

Dorcioman
  • 499
  • 5
  • 6
  • This isn't the question being posed. The OP wants to pass multiple fixtures into the same test, not generate parameters from a fixture. – PetMetz Jun 29 '22 at 15:24