fixture — Test Fixtures
A fixture is a function that prepares the data, objects, or connections needed for a test. Define it with the @pytest.fixture decorator and receive it via dependency injection through test function parameters.
Basic Fixtures
import pytest
# Define fixture
@pytest.fixture
def sample_list():
return [1, 2, 3, 4, 5]
@pytest.fixture
def user_dict():
return {"name": "Alice", "age": 30, "email": "alice@example.com"}
# Automatically injected by parameter name
def test_list_sum(sample_list):
assert sum(sample_list) == 15
def test_list_length(sample_list):
assert len(sample_list) == 5
def test_user_name(user_dict):
assert user_dict["name"] == "Alice"
def test_user_age(user_dict):
assert user_dict["age"] == 30
# Use multiple fixtures at once
def test_combined(sample_list, user_dict):
assert len(sample_list) == user_dict["age"] - 25
setup / teardown — Resource Cleanup
import pytest
import tempfile
import os
# Before yield: setup, after yield: teardown
@pytest.fixture
def temp_file():
# Setup: create temp file
fd, path = tempfile.mkstemp(suffix=".txt")
os.close(fd)
print(f"\n[Setup] Temp file created: {path}")
yield path # pass path to test
# Teardown: delete file
if os.path.exists(path):
os.remove(path)
print(f"\n[Teardown] Temp file deleted: {path}")
def test_write_file(temp_file):
with open(temp_file, "w") as f:
f.write("Hello pytest")
with open(temp_file) as f:
content = f.read()
assert content == "Hello pytest"
def test_file_exists(temp_file):
assert os.path.exists(temp_file)
# Database connection simulation
@pytest.fixture
def db_connection():
print("\n[Setup] DB connect")
conn = {"connected": True, "data": {}}
yield conn
print("\n[Teardown] DB disconnect")
conn["connected"] = False
def test_db_insert(db_connection):
db_connection["data"]["user1"] = {"name": "Bob"}
assert "user1" in db_connection["data"]
def test_db_empty_at_start(db_connection):
# Each test gets a fresh fixture instance
assert len(db_connection["data"]) == 0
Fixture Scope
import pytest
# scope: function (default), class, module, session
@pytest.fixture(scope="function")
def func_fixture():
"""Created fresh for each test"""
print("\n[function] setup")
yield {"count": 0}
print("\n[function] teardown")
@pytest.fixture(scope="module")
def module_fixture():
"""Created once per module (file)"""
print("\n[module] setup")
yield {"shared": True, "calls": 0}
print("\n[module] teardown")
@pytest.fixture(scope="session")
def session_fixture():
"""Created once for the entire test session"""
print("\n[session] setup")
config = {"env": "test", "debug": True}
yield config
print("\n[session] teardown")
def test_a(func_fixture, module_fixture, session_fixture):
func_fixture["count"] += 1
module_fixture["calls"] += 1
assert func_fixture["count"] == 1 # always 1
assert session_fixture["env"] == "test"
def test_b(func_fixture, module_fixture, session_fixture):
func_fixture["count"] += 1
module_fixture["calls"] += 1
assert func_fixture["count"] == 1 # 1 again (new instance)
assert module_fixture["calls"] == 2 # accumulated
Fixture Dependencies — Fixtures Using Fixtures
import pytest
@pytest.fixture
def base_config():
return {"host": "localhost", "port": 5432}
@pytest.fixture
def db_url(base_config):
"""Receives base_config fixture via injection"""
host = base_config["host"]
port = base_config["port"]
return f"postgresql://{host}:{port}/testdb"
@pytest.fixture
def app_config(base_config, db_url):
"""Depends on multiple fixtures"""
return {
**base_config,
"db_url": db_url,
"debug": True,
}
def test_db_url(db_url):
assert "localhost" in db_url
assert "5432" in db_url
def test_app_config(app_config):
assert app_config["host"] == "localhost"
assert "postgresql" in app_config["db_url"]
assert app_config["debug"] is True
conftest.py — Shared Fixtures
# tests/conftest.py — loaded automatically (no import needed)
import pytest
@pytest.fixture
def admin_user():
"""Available to all test files"""
return {"id": 1, "name": "Admin", "role": "admin"}
@pytest.fixture
def regular_user():
return {"id": 2, "name": "User", "role": "user"}
@pytest.fixture(scope="session")
def app_settings():
"""Shared across the entire session"""
return {
"secret_key": "test-secret",
"database_url": "sqlite:///:memory:",
"testing": True,
}
# tests/test_auth.py
def test_admin_role(admin_user):
assert admin_user["role"] == "admin"
def test_user_role(regular_user):
assert regular_user["role"] == "user"
def test_app_in_test_mode(app_settings):
assert app_settings["testing"] is True
Parameterized Fixtures
import pytest
# params: automatically runs the test for each value
@pytest.fixture(params=["sqlite", "postgresql", "mysql"])
def database(request):
"""Test runs 3 times, once per DB type"""
db_type = request.param
print(f"\n[Setup] {db_type} connect")
conn = {"type": db_type, "connected": True}
yield conn
print(f"\n[Teardown] {db_type} disconnect")
def test_db_connection(database):
# runs for sqlite, postgresql, mysql
assert database["connected"] is True
assert database["type"] in ["sqlite", "postgresql", "mysql"]
# ids: assign test names
@pytest.fixture(
params=[
(1, 1, 2),
(0, 0, 0),
(-1, 1, 0),
],
ids=["positive", "zero", "mixed"],
)
def add_case(request):
a, b, expected = request.param
return {"a": a, "b": b, "expected": expected}
def test_add(add_case):
result = add_case["a"] + add_case["b"]
assert result == add_case["expected"]
Built-in Fixtures
import pytest
# tmp_path: temporary directory (auto-cleaned)
def test_write_read(tmp_path):
file = tmp_path / "data.txt"
file.write_text("Hello pytest")
assert file.read_text() == "Hello pytest"
# tmp_path_factory: scoped temporary directory
@pytest.fixture(scope="module")
def shared_dir(tmp_path_factory):
d = tmp_path_factory.mktemp("shared")
(d / "config.json").write_text('{"key": "value"}')
return d
def test_shared_config(shared_dir):
config = (shared_dir / "config.json").read_text()
assert "key" in config
# capsys: capture print output
def test_print_output(capsys):
print("Hello")
print("World", end="")
captured = capsys.readouterr()
assert captured.out == "Hello\nWorld"
# caplog: capture log output
import logging
def test_logging(caplog):
with caplog.at_level(logging.WARNING):
logging.warning("warning message")
logging.error("error message")
assert "warning message" in caplog.text
assert len(caplog.records) == 2
# monkeypatch: temporarily replace objects/env vars
import os
def get_env_value():
return os.environ.get("MY_VAR", "default")
def test_env_patch(monkeypatch):
monkeypatch.setenv("MY_VAR", "test_value")
assert get_env_value() == "test_value"
# Automatically restored after test ends
# request: access fixture metadata
@pytest.fixture
def current_test(request):
return request.node.name
def test_knows_its_name(current_test):
assert current_test == "test_knows_its_name"
Summary
| Concept | Description |
|---|---|
@pytest.fixture | Define a fixture |
yield | Separate setup/teardown |
scope | function / class / module / session |
conftest.py | Shared fixture file |
params | Parameterize a fixture |
tmp_path | Auto-cleaned temporary directory |
monkeypatch | Replace objects at runtime |
capsys / caplog | Capture output/logs |
Fixtures are the core tool for eliminating repeated setup code and keeping tests independent.