Parametrized and factory fixtures

pytest · Advanced · 12 min

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_roles

Remember

  • 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.

Check yourself

  1. A fixture has params=[1, 2, 3] and two tests use it. How many test runs result?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.pytest.org/en/stable/how-to/fixtures.html#parametrizing-fixtures

Builds on

Unlocks

Nothing builds on this yet.