Advanced

Setting up state for a test

Some tests need a prepared object or a temporary file before they can run. A fixture builds that state once and hands it to the test, so the setup is not copy-pasted into every function.

What you will see

============================== 2 passed in 0.01s ===============================

About 2 min of reading

Quick check

Before this new lesson, one small question about the lesson you just finished. Recall it, do not look it up.

Try It

Use a fixture to give each test its own fresh state and prove the tests do not leak.

1

A fixture returns a fresh empty list, and two tests each append to it.

import pytest @pytest.fixture def fresh(): return [] def test_one(fresh): fresh.append(1) assert fresh == [1] def test_two(fresh): assert fresh == []

Before you run it, say out loud what it will print. Then open the answer.

What it prints
>>>
2

Run both. test_two still sees an empty list, proving the fixture gave it its own copy.

pytest test_fix.py

Before you run it, say out loud what it will print. Then open the answer.

What it prints
collected 2 items test_fix.py .. ============================== 2 passed in 0.01s ===============================
3

If the list were shared, test_two would see [1] and fail. The pass is the proof the state is not leaking.

pytest test_fix.py

Before you run it, say out loud what it will print. Then open the answer.

What it prints
============================== 2 passed in 0.01s ===============================
Shared mutable state across tests is a top cause of flaky suites. The fixture running per test is what keeps them independent.

Data Flow

A fixture builds fresh state, hands it to the test as an argument, and can clean up after the test finishes.
1 Fixture builds state
↓
2 Value handed to test
↓
3 Test runs against it
↓
4 Cleanup runs after
Why this works
Fixtures keep tests apart

A fixture runs fresh for each test. That is the point: one test mutating a shared object must not change what the next test sees. When tests share state, they pass alone and fail together, or the reverse, and that flakiness is the giveaway.

Setup belongs in the fixture

Put the how-to-get-ready code in the fixture, not in the test body. The test then reads as a claim about a value, and the how is named once and reused. When the setup changes, you change one fixture, not every test that needed it.

Examples

A fixture is a supplier

@pytest.fixture
def sample_list():
    return [3, 1, 2]

def test_sorted(sample_list):
    assert sorted(sample_list) == [1, 2, 3]

The fixture runs and its return is passed in as an argument. The test asks for it by name and never builds it itself.

Clean up after

@pytest.fixture
def temp_file():
    path = make_temp()
    yield path
    remove(path)

yield hands the value to the test, then the code after it runs to clean up. The file exists for the test and is gone after.

Fresh state each test

@pytest.fixture
def empty_db():
    return new_database()

The fixture runs again for each test, so no test sees another test's leftovers. Shared mutable state between tests is the classic source of flaky results.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Choosing the hard cases
Previous Lesson
Choosing the hard cases
Next Lesson
Locking a fix in place
Locking a fix in place →