Can pytest.fixture pass in argument to params that is another fixture?

71 Views Asked by At

With fixture as argument to another fixture using params it failed with:

E TypeError: 'function' object is not iterable

But use a normal function return value works fine. This works fine:

@pytest.fixture(params=data_func())

This failed:

@pytest.fixture(params=data_fixture)

Note this is an example. My actual script reads a file located in the package root. I would like to retrieve the package root via pytestconfig.rootpath. Currently I worked around this issue using environment variable but I would prefer not having to do this as I have to come up with different variable names for each project. It would be better to retrieve it from pytest if possible. I appreciate any ideas on this. Thanks.

This script generate a list of tests using the sample input:

[('mistral', 'func1'), ('mistral', 'func2'), ('llama2', 'func1'), ('llama2', 'func2')]

The following script works fine:

import pytest


def data_func():
    models = [
        "mistral",
        "llama2",
    ]
    funcs = [
        "func1",
        "func2",
    ]
    mylist = [(m, f) for m in models for f in funcs]
    print(mylist)
    return mylist

@pytest.fixture(params=data_func())
def myargs(request):
    yield request.param

def test_a(myargs):
    model, func = myargs
    print(f"model {model} func {func}")
 testings/testings6/tests/ques1.py ✓                                                                                                         25% ██▌       model mistral func func1
 testings/testings6/tests/ques1.py ✓✓                                                                                                        50% █████     model llama2 func func1
 testings/testings6/tests/ques1.py ✓✓✓                                                                                                       75% ███████▌  model llama2 func func2
 testings/testings6/tests/ques1.py ✓✓✓✓                                                                                                     100% ██████████

But using fixture as input to params failed using both yield statement:

import pytest


@pytest.fixture
def data_fixture():
    models = [
        "mistral",
        "llama2",
    ]

    funcs = [
        "func1",
        "func2",
    ]
    mylist = [(m, f) for m in models for f in funcs]
    return mylist

@pytest.fixture(params=data_fixture)
def myargs(request):
    print(request)
    #yield request.param
    yield request.getfixturevalue(request.param)

def test_a(myargs):
    model, func = myargs
    print(f"model {model} func {func}")

        pytest     = <module 'pytest' from '/home/kenneth/learning/venv_latest/lib/python3.10/site-packages/pytest/__init__.py'>
../../../venv_latest/lib/python3.10/site-packages/_pytest/fixtures.py:1324: in fixture
    params=tuple(params) if params is not None else None,
E   TypeError: 'function' object is not iterable
1

There are 1 best solutions below

3
Hai Vu On

A few comments

  1. The 'this works' and 'this fails' lines look identical to me
  2. Fixture is not the right tool for this job, you need parametrize

Here is a suggestion:

import pytest

@pytest.mark.parametrize("func", ["func1", "func2"])
@pytest.mark.parametrize( "model", ["mistral", "llama2"] )
def test_a(model, func):
    print(f"model {model} func {func}")

Output

test_me.py::test_a[mistral-func1] PASSED                  [ 25%]
test_me.py::test_a[mistral-func2] PASSED                  [ 50%]
test_me.py::test_a[llama2-func1] PASSED                   [ 75%]
test_me.py::test_a[llama2-func2] PASSED                   [100%]

Update

I see what you are trying to do: locate the external data files. By using a fixture to generate test data, you have access to the pytestconfig fixture, from which you can determine the root with pytestconfig.rootpath.

The problem is there is no way to pass this root path to data_func(). It is a catch 22 problem.

A work-around is to move the data files into the same directory where the test reside, that way, we know how to locate them. Supposed that the data reside in models.json and funcs.json, then here is how I approach this problem:

import itertools
import json
import pathlib
import pytest

# Loads the models and functions
here = pathlib.Path(__file__).parent
with open(here / "models.json") as stream:
    MODELS = json.load(stream)
with open(here / "funcs.json") as stream:
    FUNCS = json.load(stream)


@pytest.mark.parametrize(
    "model,func",
    itertools.product(MODELS, FUNCS),
)
def test_a(model, func):
    pass

Notes

  • We locate the test script's directory and place in variable here
  • From that location, we can locate the data files
  • Then we can create the parametrize with the itertools.product call