Testing against several backends or configurations, and creating many custom objects per test, needs more than a fixed fixture.
A fixture can be parametrized: @pytest.fixture(params=["sqlite", "postgres"]). Inside, request.param holds the current value, and every test that uses the fixture runs once per param. This is how you run a whole suite against several databases, serializers or configurations without touching the tests. Add ids= for readable test names.
Indirect parametrization routes @pytest.mark.parametrize values through a fixture: @pytest.mark.parametrize("user", ["admin", "guest"], indirect=True) passes each value to the user fixture as request.param, so the fixture can build the object from a short description. Useful when the test wants variations but the construction logic belongs in a fixture.
A factory fixture returns a function instead of an object: make_user(name="x", role="admin"). Tests call it as many times as they need, with different arguments, and the fixture can track created objects to clean them up after the test. Factories solve the "I need three slightly different users" problem without a fixture per variant.
Combine with care: parametrized fixtures multiply with test parametrization and with each other, so test counts can explode. Use them for genuinely important dimensions.
Example
# test_param_fixtures.py
import json, pickle
import pytest
@pytest.fixture(params=[json, pickle], ids=["json", "pickle"])
def codec(request):
return request.param
def test_roundtrip(codec): # runs twice
assert codec.loads(codec.dumps({"a": 1})) == {"a": 1}
@pytest.fixture
def make_user():
created = []
def _make(name, role="member"):
user = {"name": name, "role": role}
created.append(user)
return user
yield _make
created.clear() # cleanup all created users
def test_roles(make_user):
admin, guest = make_user("ada", "admin"), make_user("bob")
assert (admin["role"], guest["role"]) == ("admin", "member")
# $ pytest -v -> test_roundtrip[json], test_roundtrip[pickle], test_rolesRemember
- params= on a fixture multiplies every test that uses it
- request.param holds the current parameter
- indirect=True routes parametrize values through a fixture
- Factory fixtures return a function to create many custom objects
Watch out for
- Stacking several parametrized fixtures and accidentally creating hundreds of slow tests.