Learning Goals
3 min- Define a fixture with
@pytest.fixture. - Request it by naming it as a test-function argument.
- Build fixtures that depend on other fixtures.
- Share fixtures across files via
conftest.py.
Warm-Up · Setup, Injected
5 minimport pytest @pytest.fixture def account(): return BankAccount(100) # the setup def test_withdraw(account): # ← ask for it by name; pytest provides it account.withdraw(40) assert account.balance == 60
A fixture is a function that returns the thing a test needs. A test "requests" it just by listing its name as a parameter — pytest runs the fixture and passes the result in. Fresh per test (isolation), reusable everywhere, and only created when a test actually asks for it.
New Concept · @pytest.fixture
14 minDefine and request
import pytest @pytest.fixture def sample_cart(): cart = Cart() cart.add("roti", 2, 1.50) return cart def test_total(sample_cart): assert sample_cart.total() == 3.00 def test_count(sample_cart): # each test gets its OWN fresh cart assert sample_cart.count() == 1
Both tests get a brand-new cart — fixtures run per-test by default, so they're isolated like setUp.
Fixtures can use other fixtures
@pytest.fixture def db(): con = sqlite3.connect(":memory:") init_schema(con) return con @pytest.fixture def store(db): # depends on the db fixture return TaskStore(db) def test_add(store): # pytest builds db → store → injects store store.add("buy roti") assert len(store.list_all()) == 1
This composition is the big win over setUp: small fixtures combine into bigger ones, and each test pulls in exactly what it needs.
Parameters into fixtures via factory pattern
@pytest.fixture def make_user(): def _make(name, age=13): return {"name": name, "age": age} return _make # return a FUNCTION the test can call def test_user(make_user): u = make_user("Aisyah", 14) assert u["age"] == 14
Share via conftest.py
Move common fixtures to tests/conftest.py and every test in that folder can request them — no import. That's where db, client, and sample-data fixtures usually live.
Worked Example · A Fixture Hierarchy
12 min# tests/conftest.py — shared fixtures import pytest, sqlite3 from blog import BlogDB @pytest.fixture def db(): con = sqlite3.connect(":memory:") yield con # (Part 2 covers yield/teardown; ignore for now) con.close() @pytest.fixture def blog(db): b = BlogDB(db) b.init_schema() return b @pytest.fixture def blog_with_posts(blog): blog.add_post("First", "hello") blog.add_post("Second", "world") return blog
# tests/test_blog.py — tests pick the fixture at the right "level" def test_empty_blog(blog): assert blog.list_posts() == [] def test_add_post(blog): pid = blog.add_post("Hi", "there") assert blog.get_post(pid)["title"] == "Hi" def test_list_has_two(blog_with_posts): assert len(blog_with_posts.list_posts()) == 2 def test_search(blog_with_posts): assert len(blog_with_posts.search("First")) == 1
$ pytest tests/test_blog.py -v test_empty_blog PASSED test_add_post PASSED test_list_has_two PASSED test_search PASSED 4 passed
Read the diff
Three layered fixtures: db → blog → blog_with_posts. A test that needs an empty blog requests blog; one that needs data requests blog_with_posts. Each test gets exactly the right starting state with zero duplicated setup code. That composability is what makes fixtures so much more powerful than a single setUp.
Try It Yourself
13 minReplace repeated object-creation in two tests with a single fixture. Confirm both tests still pass and the code is shorter.
Build a db fixture and a store fixture that uses it. Write a test that requests only store and confirm pytest builds both.
Write a make_post(title, body) factory fixture (returns a function). Use it to create different posts in different tests.
Mini-Challenge · DRY Up a Suite
8 minTake a test file where every test rebuilds the same 3 objects. Extract a fixture hierarchy into conftest.py so each test requests only what it needs. Count the lines of setup you eliminated.
Recap
3 minA fixture (@pytest.fixture) is reusable setup; tests request it by naming it as a parameter, and pytest injects a fresh instance per test. Fixtures can depend on other fixtures, composing into exactly the state each test needs. Factory fixtures return functions for parameterised setup. Put shared ones in conftest.py. Next: fixture scope and cleanup with yield.
Vocabulary Card
- fixture
- A function providing reusable test setup, injected by name.
- dependency injection
- pytest supplying a test's requested fixtures automatically.
- fixture composition
- Fixtures that depend on other fixtures, layered up.
- factory fixture
- A fixture that returns a function, for parameterised creation.
Homework
4 minBuild a layered fixture set in conftest.py for a project of yours (e.g. db → store → store_with_data). Write ≥ 6 tests that each request the most appropriate fixture. Confirm no test repeats setup that a fixture could provide.
Model on the blog conftest. Layer fixtures so "empty" and "populated" tests each get exactly the right starting state.