Fixture scope and yield teardown

pytest · Practical · 12 min

Databases, servers and temp resources need reliable cleanup and shouldn't be recreated for every single test.

A fixture that needs cleanup uses `yield`: code before yield is setup, the yielded value goes to the test, and code after yield is teardown. Teardown runs whether the test passed or failed. If setup raises before the yield, teardown doesn't run, so acquire resources in an order that keeps that safe. Using a with block around the yield is a neat way to reuse existing context managers.

Scope controls how often a fixture is created:

  • function (default): once per test.
  • class, module, package: once per class, file or package.
  • session: once for the whole run.

Use broader scopes for expensive, effectively read-only resources: starting a database container, loading a big model, creating an app instance. Then combine them with narrow-scoped fixtures for the mutable part, for example a session-scoped database engine and a function-scoped transaction that's rolled back after each test.

The rule: a fixture can only depend on fixtures of the same or broader scope. A session fixture can't use a function fixture (pytest reports a ScopeMismatch).

Teardown order is the reverse of setup, so dependent resources are torn down before what they depend on. For complex cleanup, request.addfinalizer(fn) registers additional teardown callbacks.

Example

# test_scope.py
import pytest

log = []

@pytest.fixture(scope="module")
def database():
    log.append("connect")              # expensive: once per module
    yield {"rows": []}
    log.append("disconnect")

@pytest.fixture
def transaction(database):
    log.append("begin")
    yield database
    database["rows"].clear()           # rollback after every test
    log.append("rollback")

def test_insert(transaction):
    transaction["rows"].append(1)
    assert transaction["rows"] == [1]

def test_isolated(transaction):
    assert transaction["rows"] == []
    assert log == ["connect", "begin", "rollback", "begin"]

Remember

  • yield splits a fixture into setup and teardown
  • Teardown runs even when the test fails
  • Scopes: function, class, module, package, session
  • Fixtures may only depend on same or broader scope

Watch out for

  • Making a mutable fixture session-scoped, so one test's changes leak into others.

Check yourself

  1. A session-scoped fixture requests a function-scoped one. What happens?

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#scope-sharing-fixtures-across-classes-modules-packages-or-session