Files
lab_ai/.agents/skills/pytest/references/fixtures.md
Nguyen Duc Thao 3861b027b2
Some checks failed
K8S Fission Deployment / Deployment fission functions (push) Failing after 12s
add new
2026-01-26 23:07:28 +07:00

6.6 KiB

Fixtures

Table of Contents

Basic Fixtures

Simple Fixture

import pytest

@pytest.fixture
def user_data():
    """Return sample user data."""
    return {
        "name": "John Doe",
        "email": "john@example.com",
        "age": 30,
    }

def test_user_name(user_data):
    assert user_data["name"] == "John Doe"

def test_user_email(user_data):
    assert "@" in user_data["email"]

Fixture with Setup and Teardown

@pytest.fixture
def database():
    """Setup database, yield, then cleanup."""
    # Setup
    db = create_database()
    db.connect()

    yield db  # Test runs here

    # Teardown
    db.clear()
    db.disconnect()

def test_insert(database):
    database.insert({"id": 1, "name": "Test"})
    assert database.count() == 1

Fixture Returning Factory

@pytest.fixture
def make_user():
    """Return a factory function for creating users."""
    created_users = []

    def _make_user(name: str, email: str = None):
        user = User(name=name, email=email or f"{name}@example.com")
        created_users.append(user)
        return user

    yield _make_user

    # Cleanup all created users
    for user in created_users:
        user.delete()

def test_multiple_users(make_user):
    user1 = make_user("Alice")
    user2 = make_user("Bob")
    assert user1.name != user2.name

Fixture Scopes

# Function scope (default) - runs for each test
@pytest.fixture(scope="function")
def fresh_data():
    return {"count": 0}

# Class scope - runs once per test class
@pytest.fixture(scope="class")
def class_resource():
    return ExpensiveResource()

# Module scope - runs once per test module
@pytest.fixture(scope="module")
def module_connection():
    conn = create_connection()
    yield conn
    conn.close()

# Session scope - runs once per test session
@pytest.fixture(scope="session")
def session_config():
    return load_config()

Scope Hierarchy

session (once per test run)
  └── package (once per test package)
      └── module (once per test file)
          └── class (once per test class)
              └── function (once per test function)

Fixture Parameters

Parametrized Fixtures

@pytest.fixture(params=["sqlite", "postgresql", "mysql"])
def database_type(request):
    """Run tests with each database type."""
    return request.param

def test_connection(database_type):
    # This test runs 3 times, once for each database
    db = create_database(database_type)
    assert db.connect()

Fixture with IDs

@pytest.fixture(params=[
    pytest.param({"admin": True}, id="admin"),
    pytest.param({"admin": False}, id="regular"),
])
def user_config(request):
    return request.param

# Test output shows: test_permissions[admin], test_permissions[regular]

Indirect Parametrization

@pytest.fixture
def user(request):
    """Create user based on parameter."""
    role = request.param
    return User(role=role)

@pytest.mark.parametrize("user", ["admin", "editor", "viewer"], indirect=True)
def test_user_access(user):
    # user fixture receives each role as request.param
    assert user.role in ["admin", "editor", "viewer"]

Conftest.py

tests/conftest.py

"""Shared fixtures for all tests."""
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

# Session-scoped database engine
@pytest.fixture(scope="session")
def engine():
    return create_engine("sqlite:///:memory:")

# Function-scoped session with transaction rollback
@pytest.fixture(scope="function")
def db_session(engine):
    connection = engine.connect()
    transaction = connection.begin()
    Session = sessionmaker(bind=connection)
    session = Session()

    yield session

    session.close()
    transaction.rollback()
    connection.close()

# Shared test data
@pytest.fixture
def sample_products():
    return [
        {"name": "Widget", "price": 9.99},
        {"name": "Gadget", "price": 19.99},
        {"name": "Gizmo", "price": 29.99},
    ]

Nested Conftest Files

tests/
├── conftest.py           # Available to all tests
├── unit/
│   ├── conftest.py       # Available to unit tests only
│   └── test_models.py
└── integration/
    ├── conftest.py       # Available to integration tests only
    └── test_api.py

Built-in Fixtures

tmp_path / tmp_path_factory

def test_create_file(tmp_path):
    """tmp_path provides unique temporary directory."""
    file = tmp_path / "test.txt"
    file.write_text("Hello, World!")
    assert file.read_text() == "Hello, World!"

@pytest.fixture(scope="session")
def session_temp_dir(tmp_path_factory):
    """Create temp dir for entire session."""
    return tmp_path_factory.mktemp("session_data")

capsys / capfd

def test_print_output(capsys):
    """Capture stdout/stderr."""
    print("Hello")
    captured = capsys.readouterr()
    assert captured.out == "Hello\n"

def test_file_descriptor_output(capfd):
    """Capture at file descriptor level."""
    import os
    os.system("echo 'Hello'")
    captured = capfd.readouterr()
    assert "Hello" in captured.out

monkeypatch

def test_env_variable(monkeypatch):
    """Modify environment temporarily."""
    monkeypatch.setenv("API_KEY", "test-key")
    assert os.environ["API_KEY"] == "test-key"

def test_module_attribute(monkeypatch):
    """Modify module attribute temporarily."""
    monkeypatch.setattr("module.CONFIG", {"debug": True})
    assert module.CONFIG["debug"] is True

def test_dict_item(monkeypatch):
    """Modify dictionary temporarily."""
    monkeypatch.setitem(app.settings, "DEBUG", True)

request

@pytest.fixture
def resource(request):
    """Access test context."""
    print(f"Running: {request.node.name}")
    print(f"Module: {request.module.__name__}")
    print(f"Function: {request.function.__name__}")

    # Access fixture parameters
    if hasattr(request, "param"):
        return create_resource(request.param)

    return create_default_resource()

Autouse Fixtures

@pytest.fixture(autouse=True)
def setup_logging():
    """Automatically runs for every test."""
    logging.basicConfig(level=logging.DEBUG)
    yield
    logging.shutdown()

@pytest.fixture(autouse=True, scope="session")
def global_setup():
    """Run once at session start."""
    initialize_system()
    yield
    cleanup_system()