Test fixture

On this page 7

A test fixture is the fixed, known state of the environment that a test needs before it can run, together with the code that creates that state and removes it afterwards. In software testing, a fixture might be a set of objects in memory, rows in a database, files on disk, a configured server or a fake clock, prepared so that every run starts from the same baseline.

Fixtures make tests repeatable and independent. When each test begins from a defined state, its result depends only on the code under test, not on the order in which tests run or on data left behind by an earlier test.

How a test fixture works

Most tests follow the arrange, act, assert pattern, and the fixture is the formalized arrange step plus its cleanup. A test framework calls setup code before the test and teardown code after it, even when the test fails, so that resources such as connections, temporary files and threads are released reliably.

The term covers both the state and the mechanism. In the xUnit family, the fixture is the set of fields initialized in setUp and released in tearDown; in pytest, a fixture is a function decorated with @pytest.fixture that a test requests by naming it as a parameter, and the framework injects the prepared value. Code after a yield in a pytest fixture runs as teardown.

FrameworkSetup and teardownShared setup
JUnit 5@BeforeEach, @AfterEach@BeforeAll, @AfterAll
pytestFixture function with yieldFixture scope argument
NUnit[SetUp], [TearDown][OneTimeSetUp], [OneTimeTearDown]
xUnit.netConstructor and DisposeClass and collection fixtures
JestbeforeEach, afterEachbeforeAll, afterAll
RSpecbefore, after, letbefore(:all)
Fixture mechanisms in common frameworks

Fixture scope and lifetime

A fixture’s scope determines how often it is created and how widely it is shared. A fresh fixture built for every test offers the strongest isolation but can be slow when setup is expensive. A shared fixture created once for a class, module or whole test session saves time but introduces the risk that one test changes state that another test depends on.

ScopeCreatedBest suited for
Per testBefore every testMutable objects, in-memory data
Per classOnce for a test classRead-only reference data
Per module or fileOnce for a fileExpensive parsers or loaded models
Per sessionOnce for the whole runDatabase containers, servers
Fixture scopes and their trade-offs

A common compromise for database tests is a session-scoped database combined with a per-test transaction that is rolled back at teardown. Each test sees a clean state, yet the expensive schema creation happens only once. This technique fails when the code under test commits its own transactions or uses several connections, in which case truncating tables between tests is the safer option.

A fixture is often confused with the objects it contains or with the data files it loads. In practice, a fixture may include a mock or stub, but the fixture is the whole prepared context, while a mock is one replacement object that also verifies how it was called. A test case is the individual check that uses the fixture, and a test suite groups test cases that frequently share fixtures.

TermWhat it isRelation to the fixture
Test fixturePrepared state plus setup and teardownThe subject of this page
MockFake object that verifies interactionsMay be part of a fixture
StubFake object returning canned answersCommon fixture ingredient
Fixture fileJSON, YAML or SQL data loaded into testsOne source of fixture state
Test data builderCode that constructs valid objectsBuilds fixture objects flexibly
Test caseA single scenario with assertionsConsumes a fixture
Test fixture and neighboring terms

In web frameworks such as Ruby on Rails and Django, the word fixtures often refers narrowly to data files that load records into a test database. This is a specific kind of fixture rather than a different idea.

Designing maintainable fixtures

Good fixtures are minimal and explicit. A test should create only the data that matters for its scenario, so a reader can see why the expected result follows. Test data builders and factory functions with sensible defaults, for example a makeOrder(status="paid") helper, keep tests short while making the relevant values visible.

  • Prefer fresh fixtures for mutable state and share only data that no test modifies.
  • Keep teardown symmetric with setup, and make it safe to run even when setup failed halfway.
  • Control time and randomness in the fixture with a fixed clock and seeded generators.
  • Use real dependencies in containers for integration tests and lightweight fakes for unit tests.
  • Give fixtures intention-revealing names such as expired_subscription rather than data2.

Common fixture problems

Poorly designed fixtures are a leading cause of flaky and slow test suites. The table links typical symptoms to their usual causes and fixes.

SymptomLikely causeRemedy
Tests pass alone, fail togetherShared mutable fixtureUse per-test scope or reset state
Huge setup nobody understandsGeneral fixture for every testSplit into small, focused fixtures
Suite slows down over timeExpensive setup repeated per testWiden scope for read-only resources
Leftover files or containersTeardown skipped after errorsUse framework-managed cleanup
Tests fail on certain datesFixture depends on the real clockInject a fixed time source
Fixture smells and remedies

The pattern known as a mystery guest, where a test depends on data defined far away in a shared file, is especially costly. A failing assertion then requires searching through fixture files to understand the scenario, so keeping key values inside the test pays off in maintenance.

A non-technical view

Think of a cooking competition. Before each round, the staff reset every station: clean pans, the same ingredients, the oven at the same temperature. After the round, they clean up again. Each cook is then judged only on the cooking, not on a mess left by someone else.

A test fixture does the same job for software tests. It prepares an identical starting point before every test and tidies up afterwards, so the results are fair and repeatable.

A concrete example

A team builds a subscription billing service and writes about 400 tests for its invoice logic. Their pytest setup uses a session-scoped fixture that starts a PostgreSQL container once and applies the schema. A second, function-scoped fixture opens a transaction for each test and rolls it back at teardown, so every test starts with empty tables.

Each test then uses small builder fixtures such as active_customer and monthly_plan, plus a frozen clock set to 31 January. This makes tests for month-end proration deterministic. Before the change, the suite created the database for every test and took about twenty minutes; with the new fixture design it runs in under three minutes, and the intermittent failures caused by leftover rows disappear.