pytest using fixtures as arguments in parametrize
Asked Answered
A

9

161

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.

Autarch answered 2/2, 2017 at 23:51 Comment(2)
Issue #349 in the pytest repo calls for what you want, it's not resolved as of August 2020Percolator
Nice read: miguendes.me/…Excurvature
P
95

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
Psyche answered 14/10, 2020 at 7:8 Comment(6)
The first one didn't work for me, the second did.Alben
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 '<my_fixture at {hex id}>' not found"Assyriology
I could only get the first option to work by making the parameter a different name than the fixture itself.Dobson
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.Adlib
@Adlib I've just tried with pytest 7.4.4 and the first method worked fine without pytest.mark.usefixture do you still see the same problem?Lions
The second approach using pytest.lazy_fixture won't work if you are working with pytest 8.0.0. SourceCaiman
A
61

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.

Albaalbacete answered 4/3, 2017 at 18:21 Comment(3)
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.Autarch
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.Albaalbacete
The getfixturevalue link provided by Will appears to no longer describe that method; it appears to have been moved to API Reference.Embryology
C
16

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

Cactus answered 31/10, 2017 at 12:44 Comment(1)
Created in 2013. Looks like there's not much interest in adding this feature.Autarch
A
14

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.

Autarch answered 22/2, 2017 at 19:41 Comment(0)
C
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

Charlenecharleroi answered 30/3, 2023 at 10:13 Comment(1)
I prefer this solution.Raycher
E
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

Ebonieebonite answered 11/6, 2023 at 23:36 Comment(0)
I
0

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
Indoeuropean answered 8/9, 2023 at 9:51 Comment(0)
D
-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

Danyel answered 11/4, 2022 at 6:16 Comment(1)
This isn't the question being posed. The OP wants to pass multiple fixtures into the same test, not generate parameters from a fixture.Assyriology
J
-1

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]
Jeconiah answered 25/10, 2023 at 4:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.