# Fixtures ## Table of Contents - [Basic Fixtures](#basic-fixtures) - [Fixture Scopes](#fixture-scopes) - [Fixture Parameters](#fixture-parameters) - [Conftest.py](#conftestpy) - [Built-in Fixtures](#built-in-fixtures) ## Basic Fixtures ### Simple Fixture ```python 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 ```python @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 ```python @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 ```python # 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 ```python @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 ```python @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 ```python @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 ```python """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 ```python 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 ```python 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 ```python 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 ```python @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 ```python @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() ```