Fixture¶
Category: Testing Pattern
Overview¶
A fixture provides a fixed baseline state for tests to run against. Fixtures encapsulate test setup and teardown logic, enabling reusable test data and environment configuration. This pattern separates test preparation from test assertions, improving test clarity and reducing duplication.
Concepts¶
Test Fixture¶
Setup and teardown mechanism that establishes a known state before each test runs and optionally cleans up afterward. Commonly implemented through lifecycle methods (setUp/tearDown) or dependency injection (pytest fixtures).
Data Fixture¶
Predefined data sets used to populate databases, configure services, or provide sample inputs. Data fixtures ensure tests run against consistent, predictable data.
Object Mother / Test Data Builder¶
Factory patterns specialized for creating complex test objects with sensible defaults. Object Mother provides static factory methods, while Test Data Builder uses a fluent interface for customization.
Usage Guidelines¶
Use when:
- Multiple tests require the same setup or test data
- Test setup logic is complex enough to obscure test intent
- Tests need isolation from external dependencies (databases, APIs)
- You want to share test infrastructure across a test suite
Avoid when:
- Each test has unique setup requirements with no overlap
- Tests are simple enough that inline setup improves readability
- The fixture abstraction hides important test context
- Setup/teardown overhead exceeds the benefit for trivial tests
Implementation¶
Test Fixture (Context Manager)¶
from contextlib import contextmanager
from dataclasses import dataclass, field
from typing import Any, Generator
@dataclass
class FixtureContext:
"""Container for test state managed by a fixture."""
data: dict[str, Any] = field(default_factory=dict)
resources: list[Any] = field(default_factory=list)
@contextmanager
def database_fixture() -> Generator[FixtureContext, None, None]:
"""Fixture that sets up and tears down a test database connection."""
context = FixtureContext()
# Setup: create test database connection
context.data["db"] = {"connected": True, "tables": []}
context.resources.append("db_connection")
try:
yield context
finally:
# Teardown: close connection and cleanup
context.data["db"]["connected"] = False
context.resources.clear()
Data Fixture¶
from dataclasses import dataclass
@dataclass
class User:
"""Domain object representing a user."""
id: int
name: str
email: str
role: str = "user"
class UserFixtures:
"""Predefined user data for testing."""
@staticmethod
def admin_user() -> User:
"""Returns a standard admin user for testing."""
return User(id=1, name="Admin", email="admin@example.com", role="admin")
@staticmethod
def regular_user() -> User:
"""Returns a standard regular user for testing."""
return User(id=2, name="John Doe", email="john@example.com", role="user")
@staticmethod
def users_list() -> list[User]:
"""Returns a list of test users."""
return [
UserFixtures.admin_user(),
UserFixtures.regular_user(),
User(id=3, name="Jane Doe", email="jane@example.com"),
]
Test Data Builder¶
from dataclasses import dataclass
@dataclass
class Order:
"""Domain object representing an order."""
id: int
customer_id: int
items: list[str]
total: float
status: str
class OrderBuilder:
"""Fluent builder for creating Order test objects with sensible defaults."""
def __init__(self) -> None:
self._id: int = 1
self._customer_id: int = 100
self._items: list[str] = ["default_item"]
self._total: float = 0.0
self._status: str = "pending"
def with_id(self, order_id: int) -> "OrderBuilder":
"""Set the order ID."""
self._id = order_id
return self
def with_customer(self, customer_id: int) -> "OrderBuilder":
"""Set the customer ID."""
self._customer_id = customer_id
return self
def with_items(self, items: list[str]) -> "OrderBuilder":
"""Set the order items."""
self._items = items
return self
def with_total(self, total: float) -> "OrderBuilder":
"""Set the order total."""
self._total = total
return self
def with_status(self, status: str) -> "OrderBuilder":
"""Set the order status."""
self._status = status
return self
def completed(self) -> "OrderBuilder":
"""Configure as a completed order."""
self._status = "completed"
return self
def build(self) -> Order:
"""Build and return the Order instance."""
return Order(
id=self._id,
customer_id=self._customer_id,
items=self._items,
total=self._total,
status=self._status,
)
Usage¶
# Test Fixture usage
with database_fixture() as ctx:
assert ctx.data["db"]["connected"] is True
# Run tests with database context
# After context exits, connection is closed
# Data Fixture usage
admin = UserFixtures.admin_user()
assert admin.role == "admin"
all_users = UserFixtures.users_list()
assert len(all_users) == 3
# Test Data Builder usage
order = (
OrderBuilder()
.with_customer(42)
.with_items(["widget", "gadget"])
.with_total(99.99)
.completed()
.build()
)
assert order.status == "completed"
assert order.customer_id == 42
Trade-offs¶
Benefits:
- Reduces test code duplication through reusable setup logic
- Improves test readability by separating setup from assertions
- Ensures consistent test data across the test suite
- Test Data Builder allows creating complex objects with minimal boilerplate
Drawbacks:
- Adds indirection that may obscure what data a test uses
- Shared fixtures can create hidden dependencies between tests
- Overly generic fixtures may not fit specific test requirements
- Fixture maintenance overhead increases as the test suite grows
Real-World Examples¶
- pytest fixtures for database connections and mock services
- Django TestCase with
setUp()andtearDown()methods - Factory libraries (factory_boy, Faker) for generating test data
- Database seeding scripts for integration tests
Related Patterns¶
- Factory (fixtures often use factories internally)
- Builder (Test Data Builder is a specialized application)
- Singleton (shared fixtures may use singleton semantics)
- Template Method (fixture lifecycle follows template pattern)
API Reference¶
design_patterns.testing.fixture
¶
Fixture pattern implementations for testing.
This module provides three complementary approaches to test fixtures:
- FixtureContext / context manager fixtures: Manage setup and teardown of test state
- Data Fixtures: Provide predefined test data through static factory methods
- Test Data Builder: Create complex test objects with fluent interface
These patterns help reduce test code duplication, improve readability, and ensure consistent test data across a test suite.
FixtureContext
dataclass
¶
Container for test state managed by a fixture.
Attributes:
| Name | Type | Description |
|---|---|---|
data |
dict[str, Any]
|
Dictionary holding arbitrary test data and state. |
resources |
list[Any]
|
List of resources that need cleanup after the test. |
Example
Source code in src/design_patterns/testing/fixture.py
Order
dataclass
¶
Domain object representing an order.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
int
|
Unique order identifier. |
customer_id |
int
|
ID of the customer who placed the order. |
items |
list[str]
|
List of item names in the order. |
total |
float
|
Total price of the order. |
status |
str
|
Current order status. |
Source code in src/design_patterns/testing/fixture.py
OrderBuilder
¶
Fluent builder for creating Order test objects with sensible defaults.
Implements the Test Data Builder pattern, allowing tests to specify only the attributes relevant to the test while using reasonable defaults for everything else.
Example
Source code in src/design_patterns/testing/fixture.py
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 | |
__init__()
¶
Initialize builder with default values.
build()
¶
Build and return the Order instance.
Returns:
| Name | Type | Description |
|---|---|---|
Order |
Order
|
The constructed Order object. |
Source code in src/design_patterns/testing/fixture.py
completed()
¶
Configure as a completed order.
Returns:
| Name | Type | Description |
|---|---|---|
OrderBuilder |
OrderBuilder
|
Self for method chaining. |
with_customer(customer_id)
¶
Set the customer ID.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
customer_id
|
int
|
The customer identifier. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
OrderBuilder |
OrderBuilder
|
Self for method chaining. |
Source code in src/design_patterns/testing/fixture.py
with_id(order_id)
¶
Set the order ID.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
order_id
|
int
|
The order identifier. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
OrderBuilder |
OrderBuilder
|
Self for method chaining. |
with_items(items)
¶
Set the order items.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
items
|
list[str]
|
List of item names. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
OrderBuilder |
OrderBuilder
|
Self for method chaining. |
with_status(status)
¶
Set the order status.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
status
|
str
|
The order status string. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
OrderBuilder |
OrderBuilder
|
Self for method chaining. |
Source code in src/design_patterns/testing/fixture.py
with_total(total)
¶
Set the order total.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
total
|
float
|
The total price. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
OrderBuilder |
OrderBuilder
|
Self for method chaining. |
User
dataclass
¶
Domain object representing a user.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
int
|
Unique user identifier. |
name |
str
|
User's display name. |
email |
str
|
User's email address. |
role |
str
|
User's role (defaults to "user"). |
Source code in src/design_patterns/testing/fixture.py
UserFixtures
¶
Predefined user data for testing.
Provides static factory methods that return User instances with predetermined values, ensuring consistent test data.
Source code in src/design_patterns/testing/fixture.py
admin_user()
staticmethod
¶
Returns a standard admin user for testing.
Returns:
| Name | Type | Description |
|---|---|---|
User |
User
|
Admin user with id=1, role="admin". |
regular_user()
staticmethod
¶
Returns a standard regular user for testing.
Returns:
| Name | Type | Description |
|---|---|---|
User |
User
|
Regular user with id=2, role="user". |
Source code in src/design_patterns/testing/fixture.py
database_fixture()
¶
Context manager fixture that sets up and tears down a test database connection.
Yields:
| Name | Type | Description |
|---|---|---|
FixtureContext |
FixtureContext
|
Context containing simulated database connection state. |