How to combine fixture with parametrize

83 Views Asked by At

I have comfortably been using @pytest.mark.parametrize() in many tests now. I have not, however, succeeded in combining it with @pytest.fixture() and I cannot find a question answering this issue.

This example applies @pytest.fixture() succesfully (copied from another question that I cannot find anymore):

import pytest
def foo(data, key):
    return data[key]

@pytest.fixture()
def dict_used_by_many_tests():
    return {"name": "Dionysia", "age": 28, "location": "Athens"}

def test_foo_one(dict_used_by_many_tests):
    actual = foo(dict_used_by_many_tests, "name")
    expected = "Dionysia"
    assert actual == expected

Now, in practice I want to use @pytest.mark.parametrize().

@pytest.fixture()
def dict_used_by_many_tests():
    return {"name": "Dionysia", "age": 28, "location": "Athens"}

@pytest.mark.parametrize(
    "dict_used_by_many_tests",
    [
        (dict_used_by_many_tests),
    ],
)
def test_foo_one(dict_used_by_many_tests):
    actual = foo(dict_used_by_many_tests, "name")
    expected = "Dionysia"
    assert actual == expected

This results in the error: TypeError: 'function' object is not subscriptable.

I tried calling dict_used_by_many_tests() to work with its return value instead of the function object. This resulted in a Fixtures are not meant to be called directly error, however.

2

There are 2 best solutions below

0
AKX On BEST ANSWER

One method is request.getfixturevalue:

import pytest


@pytest.fixture()
def dict_used_by_many_tests():
    return {
        "name": "Dionysia",
        "age": 28,
        "location": "Athens",
    }


@pytest.fixture()
def dict_used_by_some_tests():
    return {
        "name": "Medusa, maybe?",
        "age": 2 << 32,
        "location": "Underworld?",
    }


@pytest.mark.parametrize(
    "fixture_name",
    [
        "dict_used_by_many_tests",
        "dict_used_by_some_tests",
    ],
)
def test_foo_one(request, fixture_name):
    d = request.getfixturevalue(fixture_name)
    # ...

Another is indirect parametrization:

import pytest


@pytest.fixture()
def character_dict(request):
    if request.param == 1:
        return {"name": "Dionysia", "age": 28, "location": "Athens"}
    elif request.param == 2:
        return {"name": "Medusa", "age": 2 << 32, "location": "Underworld"}
    raise NotImplementedError(f"No such dict: {request.param!r}")


@pytest.mark.parametrize(
    "character_dict",
    [1, 2],
    indirect=True,
)
def test_foo_one(character_dict):
    assert isinstance(character_dict, dict)
2
Axel Donath On

I think the recommended pytest pattern is to work with pytest.fixture(params=...):

import pytest

params = ["arthur", "liz", "buddy"]

@pytest.fixture(params=params)
def person(request):
    name = request.param
    return {"name": name, "age": 5}

def test_persons(person):
    assert isinstance(person["name"], str)
    assert person["age"] == 5

You can find more information on this in the pytest documentation: https://docs.pytest.org/en/7.3.x/how-to/fixtures.html#parametrizing-fixtures