Skip to content

← Testing with pytest and Hypothesis step 4 of 19

Medium Framework

Work Out Fixture Setup and Teardown Order

When a test asks for fixtures, pytest sets up each fixture's dependencies before the fixture itself. After the test, it tears them down in reverse order, so each fixture can still use its dependencies while it cleans up. If worker needs pool and pool needs db, pytest sets up db, pool and worker, then tears down worker, pool and db.

The grader cannot run pytest, so here you write that planning step yourself. Your planner sees only fixture names and what each one needs. It ignores scope and autouse.

resolve_fixture_order({"worker": ["pool"], "pool": ["db"]}, ["worker"])
# ["db", "pool", "worker"]
teardown_order(["db", "pool", "worker"])
# ["worker", "pool", "db"]

What to write

def resolve_fixture_order(
    deps: Mapping[str, Sequence[str]], requested: Sequence[str]
) -> list[str]

def teardown_order(setup: Sequence[str]) -> list[str]

deps maps each fixture name to the names it needs. requested lists the fixtures the test asks for. resolve_fixture_order returns the setup order for those fixtures and everything they need, directly or through other fixtures.

  • Put each name in the result once, after all of its dependencies.
  • A name is ready when all of its dependencies are already in the result. When several names are ready, take the smallest by Python's normal string comparison. Then work out again which names are ready.
  • Do not change deps or requested.

teardown_order returns a new list with setup in reverse order. Keep every entry, including any repeats in setup.

The alphabetical tie-break gives the tests one right answer to check. pytest does not use it.

Edge cases

  • A name that appears twice in requested or in a dependency list still appears once in the result.
  • A name that is not a key in deps has no dependencies. Real pytest reports an unknown fixture as an error. Here it is a fixture with nothing to wait for.
  • Leave out fixtures that the requested ones do not need, even if they form a cycle.
  • An empty requested returns [].

Cycles

A cycle is a loop of dependencies, such as a needing b and b needing a. If some names remain but none is ready, raise FixtureCycleError with this message:

fixture cycle: <every unresolved name, sorted, joined with ", ">

Include every name still waiting, not only the names in the loop. With deps={"a": ["b"], "b": ["a"], "client": ["a"]} and requested=["client"], the message is fixture cycle: a, b, client. client is outside the loop, but it waits for a, so it never becomes ready either. A fixture that needs itself is also a cycle.

What the driver reports

Keep solve as it is. On success it returns your setup and teardown lists and an empty error string. If your code raises FixtureCycleError, solve returns empty lists and puts the message in error. Names you ordered before you found the cycle do not appear.