Configure pytest fixtures using combination of parametrize and markers
Pytest fixture names must be unique within the whole dependency graph (#3966).
This means that when you want to parametrize fixtures, each argument name must be unique, and you have to remember which fixture uses which argument (or introduce some kind of convention):
import pytest
@pytest.fixture
def foo(foo_option):
return {'option': foo_option}
@pytest.fixture
def bar(bar_option):
return {'option': bar_option}
@pytest.mark.parametrize(
'foo_option, bar_option',
[
(42, 24),
]
)
def test_options(foo, bar):
assert foo['option'] == 42
assert bar['option'] == 24
Also, if you want to provide default vaules for options, they need to be fixtures as well:
@pytest.fixture
def foo_option():
return 'default_foo_option'
@pytest.fixture
def bar_option():
return 'default_bar_option'
@pytest.fixture
def foo(foo_option):
return {'option': foo_option}
@pytest.fixture
def bar(bar_option):
return {'option': bar_option}
def test_options(foo, bar):
assert foo['option'] == 'default_foo_option'
assert bar['option'] == 'default_bar_option'
In some cases, indirect parametrization helps, but the usage is rather verbose,
requires adding indirect
specifiers to all tests, and is a bit inconvenient
when you want to specify defaults:
@pytest.fixture
def foo(request):
default = {'option': 'default_foo_option'}
return {**defaults, **getattr(request, 'param', {})}
@pytest.fixture
def bar(request):
default = {'option': 'default_bar_option'}
return {**defaults, **getattr(request, 'param', {})}
@pytest.mark.parametrize('foo', [dict(option='custom_foo_option')], indirect=['foo'])
def test_options(foo, bar):
assert foo['option'] == 'custom_foo_option'
assert bar['option'] == 'default_bar_option'
This plugin automagically adds indirect parametrization to tests using selected fixtures, then allows specifying individual parametrization arguments, and nesting them:
@pytest.fixture(indirect=True) # HERE
def foo(request):
return request.param
@pytest.mark.parametrize('foo.first', [1]) # AND HERE
@pytest.mark.parametrize('foo.second', [2])
def test_options(foo):
assert foo['first'] == 1
assert bar['second'] == 2
# shorthand syntax
@pytest.mark.foo(first=1)
@pytest.mark.bar(second=2)
def test_options(foo):
assert foo['first'] == 1
assert bar['second'] == 2
# all fixture params
@pytest.mark.parametrize('foo.*', [dict(first=1, second=2)])
def test_options(foo):
assert foo['first'] == 1
assert bar['second'] == 2
As can be seen in the example, request.param
returns a dictionary with keys
pulled from parametrize
's extended argument name syntax: '<fixture>.<key>'
.
Note
For shorthand notation to work, marks still need to be registered.
Warning
Obviously, specifying fixture as indirect=True
makes no sense when also
passing params=...`
.
Having this, you no longer need to mark tests with parametrize(indirect=...)
:
@pytest.fixture(indirect=True)
def foo(request):
default = {'option': 'default_foo_option'}
return {**default, **request.param)
@pytest.fixture(indirect=True)
def bar(paramark):
default = {'option': 'default_bar_option'}
return {**default, **request.param)
@pytest.mark.parametrize('foo.option', ['custom_foo_option'])
def test_options(foo, bar):
assert foo['option'] == 'custom_foo_option'
assert bar['option'] == 'default_bar_option'
Also, you can group and nest such parametrizations:
@pytest.mark.foo(option=True)
class TestGroup:
def test_default(self, foo):
assert foo['option']
@pytest.mark.foo(option=False)
def test_override(self, foo):
assert not foo['option']
or, if you want to be safer and fancier:
import typing
@pytest.fixture(indirect=True)
def foo(request):
class Foo(typing.NamedTuple):
option: str = 'default_foo_option'
return Foo(**request.param)
@pytest.fixture(indirect=True)
def bar(request):
class Bar(typing.NamedTuple):
option: str = 'default_bar_option'
return Bar(**request.param)
@pytest.mark.parametrize('foo.option', ['custom_foo_option'])
def test_options(foo, bar):
assert foo.option == 'custom_foo_option'
assert bar.option == 'default_bar_option'
You can install "pytest-paramark" via pip from PyPI:
$ pip install pytest-paramark
Contributions are very welcome. Tests can be run with tox, please ensure the coverage at least stays the same before you submit a pull request.
Distributed under the terms of the MIT license, "pytest-paramark" is free and open source software
If you encounter any problems, please file an issue along with a detailed description.