Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pytest: How to parametrize a test with a list that is returned from a fixture?

Tags:

python

pytest

I want to parametrize a test with a list which is created dynamically by a fixture like so:

@pytest.fixture
def my_list_returning_fixture(depends_on_other_fixtures):
    return ["a dynamically created list which", depends_on_other_fixtures]

How can I achieve that? Alternatively, how can I ensure that a certain fixture gets called first - this would solve this problem before it even occurs.


What I already tried

  • I tried to parametrize the test with the fixture (which just results in errors because python thinks I want to hand over the function itself):

    @pytest.mark.parametrize(
        "an_element_from_the_list_of_my_fixture", 
        my_list_returning_fixture
    )
    def test_the_list(an_element_from_the_list_of_my_fixture):
        print(an_element_from_the_list_of_my_fixture)
    

    Calling the fixture like a normal function like my_list_returning_fixture() just results in errors, too! Python doesn't know how to fill the "parameters" of the fixture (which are actually just other fixtures) and displays an error message about too few passed arguments...

    Therefore I need pytest to inject the depends_on_other_fixtures dependencies, so I can't call it like a normal function.

  • I also tried to interpose another fixture between the list fixture and the test like so:

    @pytest.fixture(params=my_list_returning_fixture)
    def my_interposed_parametrized_fixture(request):
        return request.param
    
    def test_the_list(my_interposed_parametrized_fixture):
        ...
    
  • I also tried to play around with indirect parametrization but it didn't work either...


It's easy with static lists

I know it is easily possible to parametrize a test with a given list like so:

the_list = [1, 2, 3]
@pytest.mark.parametrize("an_element_from_the_list", the_list)
def test_the_list(an_element_from_the_list):
    print(an_element_from_the_list)

This will lead to three tests. One for each element in the list.

like image 210
winklerrr Avatar asked Oct 11 '18 15:10

winklerrr


People also ask

Can we use fixture in parametrize pytest?

You can use such a simple parametrized fixture for more complex fixtures by combining the param value with some kind of factory. You can also alias the call to parametrize: numbers = pytest.

Can fixtures be parameterized?

parametrize allows one to define multiple sets of arguments and fixtures at the test function or class.

Which annotation is used for parameterization in pytest?

you can put @pytest. mark. parametrize style parametrization on the test functions to parametrize input/output values as well.


2 Answers

This seems to be a duplicate of this question.

By design, this is not possible: the concept of "fixtures" is reserved to test execution, while the concept of "parameters" is reserved to test collection. Please see my detailed answer here.

like image 136
smarie Avatar answered Sep 21 '22 13:09

smarie


The short answer is that you can't do it the way you want, i.e., through fixtures: https://github.com/pytest-dev/pytest/issues/2155. Basically, the number of things yielded or returned has to be known up front for pytest to properly compute the fixture and test dependency graph.

It appears that the only way is to fix the list elements before passing them to any of pytests's decorators. Here is an example, related to your other question, showing that the problem can not be solved by say a generator:

import pytest

def gen_lines():
    with open('file_that_does_not_exist') as f:
        yield from f

@pytest.fixture(scope='session')
def create_file():
    with open('file_that_does_not_exist', 'w') as f:
        print('ABC', file=f)
        print('DEF', file=f)

@pytest.fixture(params=gen_lines())
def line_fixture(request):
    return request.param

def test_line(line_fixture):
    assert True

This will fail at collection time when pytest turns your generator into a list. Adding a dependency to line_fixture on create_file won't help either for the same reason.

Your only real option at this point is to run create_file at module load time or before.

import pytest

def gen_lines():
    with open('file_that_does_not_exist') as f:
        yield from f

def create_file():
    with open('file_that_does_not_exist', 'w') as f:
        print('ABC', file=f)
        print('DEF', file=f)

create_file()

@pytest.fixture(params=gen_lines())
def line_fixture(request):
    return request.param

def test_line(line_fixture):
    assert True

The list does not have to be static per-se. It just can't be created by a fixture. But don't let that stop you. You can put the code for defining and running create_file into a separate module, and just import it wherever you need it as a utility. That will obscure all the messy details and make your code look about as clean as it would with fixtures.

like image 23
Mad Physicist Avatar answered Sep 20 '22 13:09

Mad Physicist