Introduction

Pytest fixtures have scopes (function, class, module, package, session) that determine when they are created and destroyed. A narrower-scoped fixture cannot request a wider-scoped one in ways that violate teardown order, and fixtures with teardown logic (yield fixtures) may not execute cleanup in the expected order when multiple fixtures are involved. The most common errors are ScopeMismatch: You tried to access the function scoped fixture with a session scoped request and fixtures whose teardown runs after dependent resources have already been cleaned up by another fixture's teardown.

Symptoms

bash
ScopeMismatch: You tried to access the 'function' scoped fixture 'db_session' with a 'session' scoped request object, involved factories:
  tests/conftest.py:12:  def session_fixture(db_session)

Or teardown ordering issues:

bash
Error: database connection already closed
# Fixture A closes DB in teardown, but Fixture B tries to clean up data after

Common Causes

  • Scope mismatch: Session-scoped fixture requesting function-scoped fixture
  • Teardown order not guaranteed: Multiple yield fixtures may teardown in unexpected order
  • Fixture overridden in conftest.py: conftest at different levels creates conflicting fixtures
  • Autouse fixtures with side effects: Autouse fixtures run unexpectedly in nested tests
  • Fixture not found: Typo in fixture name or fixture defined in wrong conftest.py
  • Generator fixture not yielding: Fixture uses return instead of yield, teardown skipped

Step-by-Step Fix

Step 1: Match fixture scopes correctly

```python import pytest

# WRONG: Session fixture cannot request function-scoped fixture # @pytest.fixture(scope='session') # def session_data(db_session): # db_session is function-scoped # return db_session.query(User).all()

# CORRECT: Match scopes or use wider scope for dependencies @pytest.fixture(scope='session') def db_engine(): engine = create_engine('sqlite:///test.db') yield engine engine.dispose()

@pytest.fixture(scope='function') def db_session(db_engine): session = Session(bind=db_engine) yield session session.rollback() session.close() ```

Step 2: Control teardown order with nested fixtures

```python @pytest.fixture(scope='session') def app(): """Create application (tears down last).""" app = create_app() yield app app.shutdown()

@pytest.fixture(scope='function') def client(app): """Create test client (tears down before app).""" with app.test_client() as client: yield client

@pytest.fixture(scope='function', autouse=True) def clean_db(db_session): """Clean database before and after each test (tears down first).""" # Setup: truncate tables db_session.execute(text('TRUNCATE users, orders RESTART IDENTITY CASCADE')) db_session.commit() yield # Teardown: runs before client teardown, which runs before app teardown db_session.rollback() ```

Step 3: Use fixture factories for complex setups

```python @pytest.fixture def user_factory(db_session): """Factory fixture that creates users on demand.""" created_users = []

def _create_user(**kwargs): user = User( name=kwargs.get('name', 'test_user'), email=kwargs.get('email', 'test@example.com'), ) db_session.add(user) db_session.commit() created_users.append(user.id) return user

yield _create_user

# Teardown: clean up all created users for user_id in created_users: db_session.query(User).filter_by(id=user_id).delete() db_session.commit()

# Usage in tests def test_user_workflow(user_factory): user1 = user_factory(name='alice') user2 = user_factory(name='bob') assert user1.name == 'alice' ```

Prevention

  • Keep fixture scopes aligned: wider-scoped fixtures should only depend on same-or-wider scopes
  • Use yield fixtures for proper teardown instead of addfinalizer
  • Document fixture dependencies and scope in conftest.py with docstrings
  • Use pytest --fixtures to inspect available fixtures and their scopes
  • Avoid autouse fixtures unless the setup is genuinely needed by all tests in scope
  • Use fixture factories instead of complex fixture hierarchies when possible
  • Run tests with --setup-show to visualize fixture setup and teardown order