How to concatenate several parametrized fixtures into a new fixture in py.test?
There is now a solution available in pytest-cases
, named fixture_union
. Here is how you create the fixture union that you requested in your example:
from pytest_cases import fixture_union, pytest_fixture_plus@pytest_fixture_plus(params=[1, 2, 3])def lower(request): return "i" * request.param@pytest_fixture_plus(params=[1, 2])def upper(request): return "I" * request.paramfixture_union('all', ['lower', 'upper'])def test_all(all): print(all)
It works as expected:
<...>::test_all[lower-1] <...>::test_all[lower-2] <...>::test_all[lower-3] <...>::test_all[upper-1] <...>::test_all[upper-2]
Note that I used pytest_fixture_plus
in the above example because if you use pytest.fixture
you will have to handle yourself the cases where a fixture is not actually used. This is done as follows, for example for the upper
fixture:
import pytestfrom pytest_cases import NOT_USED@pytest.fixture(params=[1, 2])def upper(request): # this fixture does not use pytest_fixture_plus # so we have to explicitly discard the 'NOT_USED' cases if request.param is not NOT_USED: return "I" * request.param
See documentation for details. (I'm the author by the way ;) )
I had the exact same question (and received a similar, but distinct answer). The best solution I was able to come up with was to reconsider how I parametrize my tests. Instead of having multiple fixtures with compatible outputs, I used the fixtures as regular functions, and just parametrized your meta-fixture to accept the function name and arguments:
import pytestdef lower(n): return 'i' * ndef upper(n): return 'I' * n@pytest.fixture(params=[ (lower, 1), (lower, 2), (upper, 1), (upper, 2), (upper, 3),])def all(request): func, *n = request.param return func(*n)def test_all(all): ...
In your particular case, unpacking n
into a list and passing it with *
is slightly overkill, but it provides generality. My case has fixtures that all accept different parameter lists.
Until pytest allows us to properly chain fixtures, this is the only way I have come up with to run 5 tests instead of 12 in your situation. You can make the list shorter with something like
@pytest.fixture(params=[ *[(lower, i) for i in range(1, 3)], *[(upper, i) for i in range(1, 4)],])
There is an actual advantage of doing it this way. You can pick and chose which tests you want to do special things to, like XFAIL, without affecting a whole swath of other tests if you have additional dependencies in your pipeline.
It is not beautiful, but may be today you know the better way.
Request object inside 'all' fixture know only about own params: 'lower', 'upper'. One way using fixtures from a fixture function.
import pytest@pytest.fixture(params=[1, 2, 3])def lower(request): return "i" * request.param@pytest.fixture(params=[1, 2])def upper(request): return "I" * request.param@pytest.fixture(params=['lower', 'upper'])def all(request, lower, upper): if request.param == 'lower': return lower else: return upperdef test_all(all): assert 0, all