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?
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
As far as I know, you can't. There are a few workarounds:
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.
@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
@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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With