Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I pass fixtures to pytest.mark.parameterize?

Tags:

python

pytest

I am trying to pass three different fixtures to my pytest.mark.parameterize decorator, like this:

@pytest.mark.parametrize("credentials, return_code", [
    (user1, 200),
    #(user2, 200),
    #(user3, 401)
])
def test_login():
    assert True
# Actual test uses response.return_code == return_code

However, I get an error that reads:

NameError: name 'user1' is not defined

This isn't correct though, I have defined it in conftest.py:

user1_info = [('user1', 'password')]
@pytest.fixture(params=user1_info)
def user1(request):
    return request.param

The fixture works if I change my test to be like this:

def test_login(user1):
    assert True
# Actual test uses response.return_code == return_code

The problem is that if I do it this way, I have to write three separate tests, one for each fixture, and for each status_code check. If I could use parameterize, I could use one function and check the user/expected return code.

How can I utilize my fixtures are part of the pytest.mark.parameterize decorator?

like image 297
NewGuy Avatar asked Dec 09 '17 06:12

NewGuy


Video Answer


2 Answers

To pass fixtures as parametrized arguments, reference them as strings and use the keyword arguments indirect=['arg_name', ...].

Then use the parameter names credentials and return_code as arguments to your test function.

@pytest.mark.parametrize(
    "credentials, return_code",
    [
        ("user1", 200),
        ("user2", 200),
        ("user3", 401),
    ],
    # Only the `credentials` parameter is a reference to the
    # fixture. The `return_code` should be passed as is.
    indirect=["credentials"],
)
def test_login(credentials, return_code):
    return True

The documentation for pytest's parametrize magic with indirect can be found here: https://docs.pytest.org/en/stable/example/parametrize.html#apply-indirect-on-particular-arguments

like image 114
damon Avatar answered Sep 18 '22 14:09

damon


As far as I know, you can't. There are a few workarounds:

1: forego parametrization

def test_login():
    for user, return_code in [
      (User("user1", ...), 200),
      (User("user2", ...), 200),
      (User("user3", ...), 401),
    ]:
        # test user
        pass

or

def test_login(user1, user2, user3):
    for user, return_code in [
      (user1, 200),
      (user2, 200),
      (user3, 401),
    ]:
        # test user
        pass

Pro: simple
Con: If the first test fails, the later tests don't run. The error message also doesn't show which test case failed.

2: parametrize with an index or name

@pytest.mark.parametrize('idx', [0,1,2])
def test_login(idx):
    user, return_code = [
      (User("user1", ...), 200),
      (User("user2", ...), 200),
      (User("user3", ...), 401),
    ][idx]
        # test user
        pass

(or the equivalent using the user1, user2, user3 fixtures)

Pro: pytest recognizes the separate tests
Con: useless indexes/names

3: use an indirect parametrization fixture

@pytest.mark.fixture
def user_and_code(request):
    if request.param == "user1":
        return User("user1", ...), 200
    elif request.param == "user2":
        return User("user2", ...), 200
    elif request.param == "user3":
        return User("user3", ...), 401

@pytest.mark.parametrize('user_and_code', ['user1', 'user2', 'user3'], indirect=True)
def test_login(user_and_code):
    user, return_code = user_and_code

    # test user
    pass

Or

@pytest.mark.fixture
def user_and_code(request, user1, user2, user3):
    return [
        (user1, 200),
        (user2, 200),
        (user3, 401),
    ][request.param]

@pytest.mark.parametrize('user_and_code', [0,1,2], indirect=True)
def test_login(user_and_code):
    user, return_code = user_and_code

    # test user
    pass

Pro: test code is clean, fixture can be reused in other tests
Con: more complexity

According to the docs the indirect parametrization is meant to skip expensive setup during test collection, not so much to make fixtures available to parameters, which as you can see doesn't work very well.

There are a few choices that can be mixed and matched in each of the above options, e.g. instantiating all users or using if/else to only instantiate what you need, using the user1 etc fixtures or creating users inline, using names or indexes, etc. The examples only show some of the alternatives.

like image 22
JanKanis Avatar answered Sep 20 '22 14:09

JanKanis