Stop Repeating Yourself in Tests: A Clear Guide to Fixtures in Python
Fixtures may look simple — but used properly, they can completely change how you design your test suite.
Stop Repeating Yourself in Tests: A Clear Guide to Fixtures in Python
Fixtures may look simple — but used properly, they can completely change how you design your test suite.

AI generated
Most developers learn pytest fixtures early — but many never realize how powerful they become once a project grows.
When you first start writing tests in Python, things usually feel straightforward. A few test functions, a few assertions, and everything works nicely.
But as the project grows, tests begin to require more setup: a database connection, a configured API client, prepared test data, or temporary files. Soon, you notice the same setup code appearing in multiple tests.
This repetition makes tests harder to maintain and more fragile.
If you’re using pytest, there's a clean solution to this problem: fixtures.
In this article, we’ll explore what fixtures are, why they exist, and how to use them effectively to keep your test suite clean and maintainable.
The Problem: Repeated Test Setup
Let’s start with something simple.
def add(a, b):
return a + b
A basic test might look like this:
def test_add():
assert add(5, 3) == 8
Easy.
Now imagine a slightly more realistic example.
class User:
def __init__(self, name, age):
self.name = name
self.age = age
def is_adult(self):
return self.age >= 18
Without fixtures, your tests might look like this:
def test_user_is_adult():
user = User("Alice", 20)
assert user.is_adult() is True
def test_user_is_not_adult():
user = User("Bob", 15)
assert user.is_adult() is False
Still manageable.
But imagine the User object now requires:
- database access
- password hashing
- an external service
- multiple configuration steps
Copy-pasting setup code across tests quickly becomes messy.
This is exactly the problem fixtures solve.
What Is a Fixture?
A fixture is a reusable setup function for your tests.
In pytest, a fixture:
- prepares the environment needed by a test
- provides data or resources to that test
- can optionally perform cleanup afterward
You can think of a fixture as a helper that prepares something your test needs and provides it automatically.
Your First Fixture
Let’s rewrite the previous example using fixtures.
import pytest
@pytest.fixture
def adult_user():
return User("Alice", 20)
@pytest.fixture
def minor_user():
return User("Bob", 15)
Now the tests become simpler:
def test_user_is_adult(adult_user):
assert adult_user.is_adult() is True
def test_user_is_not_adult(minor_user):
assert minor_user.is_adult() is False
I highly recommend running these examples locally to really see how pytest executes fixtures and injects them into your tests.
Notice something interesting: we never call adult_user() ourselves.
Instead, pytest:
- sees that the test requires
adult_user - runs the fixture
- injects the returned value into the test function
This mechanism is known as dependency injection, and it’s one of the reasons pytest feels so elegant.
How Fixture Injection Works
When pytest encounters a test like this:
def test_something(my_fixture):
It searches for a fixture named my_fixture.
If it finds one, pytest:
- executes the fixture function
- takes its return value
- passes that value as an argument to the test
This automatic wiring allows tests to stay short and expressive.
Fixtures Can Depend on Other Fixtures
One powerful feature of pytest is that fixtures can depend on other fixtures.
For example:
@pytest.fixture
def user():
return {"name": "Alice"}
@pytest.fixture
def authenticated_user(user):
user["authenticated"] = True
return user
Now any test that requests authenticated_user will automatically receive the result of the user fixture as well:
def test_auth(authenticated_user):
assert authenticated_user["authenticated"] is True
Pytest automatically resolves this dependency chain for you.
This allows you to build small, reusable pieces of setup and compose them into more complex scenarios.
Setup and Teardown with yield
Many real-world test setups require cleanup.
For example:
- opening a file and closing it after the test
- connecting to a database and disconnecting later
- starting a temporary server and shutting it down afterward
Fixtures support this pattern using yield.
@pytest.fixture
def temp_file():
f = open("test.txt", "w")
yield f
f.close()
The fixture works in two stages:
- Code before
yieldprepares the resource. - The value produced by
yieldis provided to the test. - Code after
yieldruns once the test finishes and performs cleanup.
Example usage
def test_write_to_file(temp_file):
temp_file.write("Hello tests!")
Here is what happens during execution:
- The fixture opens
test.txt. - The file object is passed to the test.
- The test writes data to the file.
- After the test finishes, the fixture closes the file automatically.
This pattern makes resource management both clear and safe.
Controlling How Often Fixtures Run (Scope)
By default, fixtures run once per test function.
However, pytest allows you to control this behavior using scopes.
@pytest.fixture(scope="module")
def resource():
print("Setting up resource")
return {}
Available scopes include:
- function (default): runs for every test
- class: runs once per test class
- module: runs once per test file
- session: runs once for the entire test session
Scopes are especially useful for expensive operations like:
- creating a test database
- loading large datasets
- establishing external connections
Fixtures vs. setUp() in unittest
If you’ve used Python’s built-in unittest framework, you may be familiar with setUp():
def setUp(self):
...
Pytest fixtures offer several advantages:
- reusable across multiple test files
- support dependency injection
- fixtures can depend on other fixtures
- flexible scoping options
This flexibility is one reason many developers prefer pytest for modern Python projects.
A Real-World Use Case
In web applications such as Django, Flask, or FastAPI, fixtures are commonly used to:
- create test users
- provide an API client
- set up a test database
- insert sample records
Example:
from fastapi.testclient import TestClient
from my_app import app # your FastAPI application instance
@pytest.fixture
def client():
return TestClient(app)
Here, app is your web application instance (for example, a FastAPI or Flask app).
The fixture creates a test client that allows your tests to send HTTP requests to the application without running a real server.
Tests that need the client simply declare it:
def test_homepage(client):
response = client.get("/")
assert response.status_code == 200
Clean, reusable, and scalable.
When Should You Use Fixtures?
Fixtures are helpful when:
- you repeat setup logic across tests
- setup becomes complex
- cleanup is required after tests
- you want isolated, maintainable tests
However, avoid introducing fixtures when setup is trivial. Simplicity should always remain the priority.
A Simple Mental Model
A useful mental model is:
A fixture prepares something your test needs and provides it automatically.
Or even simpler:
Fixtures give tests what they need.
Why Fixtures Matter in Large Test Suites
In small examples, fixtures may look like a convenience feature.
In large projects, they become essential.
Without fixtures, test suites often suffer from:
- duplicated setup logic
- fragile tests that break when setup changes
- hard-to-maintain test files
Fixtures solve this by centralizing setup logic and keeping tests focused on behavior rather than preparation.
Final Thoughts
Fixtures aren’t just a convenience — they change how you think about testing. Instead of focusing on setup, you focus on behavior.
In practice, this means:
- less duplicated code
- clearer, more focused tests
- safer handling of resources
- easier scaling as your project grows
If you’re using pytest without taking advantage of fixtures, you're missing one of its most powerful tools.
Once you start thinking in terms of fixtures, your tests stop feeling like small scripts — and start behaving like well-designed systems.
메타데이터
- post_id
- b480a053e93b
- slug
- stop-repeating-yourself-in-tests-a-clear-guide-to-fixtures-in-python-b480a053e93b
- url
- https://python.plainenglish.io/stop-repeating-yourself-in-tests-a-clear-guide-to-fixtures-in-python-b480a053e93b
- canonical_url
- https://python.plainenglish.io/stop-repeating-yourself-in-tests-a-clear-guide-to-fixtures-in-python-b480a053e93b
- author_url
- https://medium.com/@bytehaven
- status
- ok
- fetched_at
- 2026-06-09 15:37:30