# What is a test fixture?

A test fixture is the fixed state a test starts from, e.g. prepared data, which is set up before the test runs and cleaned up after it finishes.

Last updated September 29, 2026, 8 min read

## Learning objectives

After reading this article you will be able to:

-   Define a test fixture
-   Explain setup and teardown
-   Compare cleaning up after tests with resetting before them

## Related content

-   [What is software testing?](https://specstory.com/learning/testing/software-testing)
-   [What is a test harness?](https://specstory.com/learning/testing/test-harness)
-   [What is the difference between mocks, stubs, and fakes?](https://specstory.com/learning/testing/mocks-vs-stubs)
-   [What is unit testing?](https://specstory.com/learning/testing/unit-testing)
-   [What is integration testing?](https://specstory.com/learning/testing/integration-testing)

## What is a test fixture?

A test fixture is the known state a test needs before it runs, plus the code that creates that state and removes it afterward. The state can be data or an environment, e.g. a test database that holds one customer with 2 items in the cart. A fixture makes a test repeatable, because each run starts from the same state.

The International Software Testing Qualifications Board (ISTQB) [defines a test fixture](https://glossary.istqb.org/en_US/term/test-fixture) as the predefined data and test environment that let software be tested in a repeatable way. In pytest, a fixture is the function that builds the state and hands it to the test. In Ruby on Rails and Django, a fixture is a file of database records that loads before tests run.

In [software testing](https://specstory.com/learning/testing/software-testing), a fixture is one part of a [test harness](https://specstory.com/learning/testing/test-harness), the code that runs tests and checks their results. A fixture differs from a [test double](https://specstory.com/learning/testing/mocks-vs-stubs), e.g. a mock, which replaces a component that the code under test calls. A fixture can still build a mock and hand it to a test. In electronics manufacturing, "test fixture" also names a physical device that holds a circuit board while it is tested.

## How do setup and teardown work?

Setup is the step that builds a fixture, and teardown is the step that removes it. A test runner puts each test between the two steps:

1.  The runner runs the setup code, e.g. code that adds a customer to a test database.
2.  The test runs the code under test from that state and checks the result.
3.  The runner runs the teardown code, which removes what setup built, whether the test passed or failed.

Setup is the arrange step of [arrange-act-assert](https://specstory.com/learning/glossary#arrange-act-assert), moved out of the test so several tests can reuse it. Test frameworks name the two steps differently:

| Framework | Setup | Teardown |
| --- | --- | --- |
| pytest | Fixture code before `yield` | Fixture code after `yield` |
| Python `unittest` | `setUp` method | `tearDown` method |
| JUnit 5 | `@BeforeEach` method | `@AfterEach` method |
| Jest | `beforeEach` function | `afterEach` function |
| Playwright Test | Fixture code before `await use()` | Fixture code after `await use()` |

In pytest, a test requests a fixture by naming it as a parameter. A fixture in a `conftest.py` file is available to the tests in that folder and below it. After a test, [pytest runs teardowns](https://docs.pytest.org/en/stable/how-to/fixtures.html) in the reverse order of their setups.

A fixture's scope sets how often setup and teardown run. The default pytest scope, `function`, builds a fresh fixture for each test. The `class`, `module`, `package`, and `session` scopes share one fixture across a class, a file, a package, or the whole run, which saves setup time. A [unit test](https://specstory.com/learning/testing/unit-testing) usually needs a small fixture in memory, while an [integration test](https://specstory.com/learning/testing/integration-testing) often needs a database with known rows.

Diagram: Setup and teardown around two tests

Each test gets a fresh fixture with function scope. A fixture with session scope is built once and shared, so a change one test makes to it reaches the next test.

## What is an example of a test fixture?

Here is an illustrative example. Acme Co. sells furniture online. A developer asks a [coding agent](https://specstory.com/learning/ai-coding/coding-agent) to "Let customers edit their delivery address during checkout." The agent changes the checkout code. Acme keeps a fixture file, `tests/fixtures/cart.json`, that holds the cart each checkout test starts with:

```json
[
  {"sku": "oak-chair", "quantity": 1, "price": "120.00"},
  {"sku": "oak-stool", "quantity": 1, "price": "120.00"}
]
```

A pytest fixture loads that file for a fresh customer, and the test checks the address change:

```python
import json
import pytest

@pytest.fixture
def customer(db):
    c = db.create_customer(email="buyer@example.com")
    with open("tests/fixtures/cart.json") as f:
        c.cart.load(json.load(f))  # 2 items, $240.00 in all
    yield c
    db.delete_customer(c.id)  # teardown

def test_edit_address_keeps_cart(customer):
    customer.checkout.set_address("12 Elm Street")
    assert customer.address == "12 Elm Street"
    assert len(customer.cart.items) == 2
```

The run goes through these steps:

1.  pytest reads the test's parameters and finds `customer`, which in turn requests a `db` fixture from `conftest.py`.
2.  The `db` fixture connects to the test database.
3.  The `customer` fixture creates a customer and loads the 2 items from the fixture file.
4.  The test changes the address to 12 Elm Street, and the address check passes.
5.  The cart check fails. The address saved, but the cart emptied.
6.  pytest still runs the code after `yield`, which deletes the customer, and then the `db` fixture closes its connection.
7.  The next test starts with no customer left over from this one.

pytest prints the values that differed:

```text
>       assert len(customer.cart.items) == 2
E       assert 0 == 2
```

The fixture makes the cart check possible. Because each run starts with exactly 2 items, the test can expect exactly 2 afterward. This example is simplified. A real suite would give each test its own email address, so tests that run in parallel do not collide.

## What changes when a coding agent writes the code?

When a coding agent writes the code, the tests, and their fixtures in one session, it generates all three from the same prompt and context. The fixture can then leave out the case the code gets wrong. Suppose the agent's fixture held an empty cart, and its test checked that the cart keeps its items. That test would pass on the broken code, with 0 items before and after.

An empty cart can reach a test even when a shared fixture in `conftest.py` loads 2 items. In pytest, a fixture defined in a test file overrides a `conftest.py` fixture of the same name for the tests in that file. If an agent adds its own `customer` fixture to its test file, those tests still request `customer` but start from the agent's state. The test functions match the rest of the suite, so a reviewer who reads only them can miss the change.

A team can review an added or changed fixture as closely as a changed check, and ask why a test file redefines a fixture from `conftest.py`.

## How do fixtures cause flaky tests?

A fixture causes a [flaky test](https://specstory.com/learning/debugging/flaky-tests) when a test's starting state changes between runs of the same code. Four common mistakes cause that change:

-   **Teardown that never runs.** In pytest, a fixture that fails before its `yield` skips its teardown, so what it built stays. A killed test process runs no teardown, so the next Acme run can fail to create a customer whose email is still stored.
-   **A shared fixture that tests change.** A test that edits a fixture with session scope changes the start of each later test that uses it. Results depend on test order, which breaks [test isolation](https://specstory.com/learning/environments/test-isolation).
-   **Values that change on each run.** A fixture that reads the clock or generates random data, e.g. an order date, gives each run a different start.
-   **State the fixture never made.** A test can pass on rows left in a laptop's database and fail on a fresh runner in continuous integration (CI). That is one reason tests [pass locally but fail](https://specstory.com/learning/ci-cd/tests-pass-locally-fail-in-ci) in CI.

A fixture also checks nothing by itself. A test with a careful fixture and a weak check still passes on broken code.

## How is cleaning up after a test different from resetting before it?

Cleaning up after a test, the teardown, removes what setup built and what the test added. Resetting before a test restores a known starting state, whatever earlier tests left. Teardown keeps each test's changes inside that test, but a crash or a killed run skips it. A reset does not depend on earlier tests, but it costs time and can hide a test that leaves records behind.

The [Cypress best practices](https://docs.cypress.io/app/core-concepts/best-practices) recommend resetting state before tests, because an `after` hook may not run. Leftover state also shows a person where a failed test stopped.

A suite can do both. Each run starts by [seeding](https://specstory.com/learning/environments/test-data-seeding) a fresh database, and each test removes its own records. Wrapping each test in a database transaction that rolls back does both jobs in one step. The rollback works only when the code under test uses the test's connection and does not commit that transaction.

## FAQs

### What is a pytest fixture?

A pytest fixture is a function that pytest runs to build a state or an object for a test. A test requests the fixture by naming it as a parameter. Code after the fixture's yield statement runs as teardown when its scope ends, which for the default scope is when the test finishes, even if it fails.

### What is the difference between a fixture and a mock?

A fixture is the starting state a test needs, e.g. a customer record in a test database. A mock is a kind of test double, which replaces a component that the code under test calls.

### Should tests share fixtures?

Tests should share fixture code, but they should usually not share fixture state that they change. A fixture with function scope gives each test a fresh copy. A fixture with a wider scope saves setup time, but a change to it reaches later tests, so it suits data that no test edits.

### What is a fixture file?

A fixture file is a file of fixed sample data that a test loads during setup, e.g. a JSON file that holds the items in a cart. In Ruby on Rails and Django, fixture files hold database records. A change to one changes the state that tests start from and deserves the same review as a changed check.

---

Source: [What is a test fixture? | Setup and teardown | SpecStory](https://specstory.com/learning/testing/test-fixture)
