Setup Pytest Fixtures
Purpose
Creates pytest fixtures following project-watch-mcp patterns: factory fixtures for customization, async fixtures for async code, centralized organization in tests/utils/, and proper conftest.py hierarchy.
Quick Start
Create a basic fixture:
import pytest
@pytest.fixture
def mock_settings():
"""Create mock settings for testing."""
settings = MagicMock()
settings.project = MagicMock()
settings.project.project_name = "test_project"
return settings
Create a factory fixture:
@pytest.fixture
def mock_settings_factory():
"""Factory for custom mock settings."""
def create_settings(**kwargs):
settings = MagicMock()
settings.project = MagicMock()
settings.project.project_name = kwargs.get("project_name", "test_project")
return settings
return create_settings
Instructions
Step 1: Identify Fixture Type
Determine which fixture pattern to use:
- •
Basic Fixture: Simple, reusable test data
- •Use for: Constant values, simple mocks
- •Example:
mock_settings_minimal
- •
Factory Fixture: Customizable with parameters
- •Use for: Configurable test data, parameterized tests
- •Example:
mock_settings_factory(**kwargs)
- •
Async Fixture: For async operations
- •Use for: Async setup/teardown, async resources
- •Example:
@pytest_asyncio.fixture async def...
- •
Scoped Fixture: Shared across multiple tests
- •Use for: Expensive setup, session-wide resources
- •Example:
@pytest.fixture(scope="session")
Step 2: Choose Organization Strategy
Centralized Utilities (Recommended for reusable fixtures):
- •Location:
tests/utils/ - •Files:
mock_settings.py,mock_services.py,mock_drivers.py,environmental_helpers.py - •Import in:
tests/conftest.pyto make available project-wide
Local Conftest (For layer-specific fixtures):
- •Location:
tests/unit/conftest.py,tests/integration/conftest.py,tests/e2e/conftest.py - •Use for: Fixtures specific to that test layer
Test File (For test-specific fixtures):
- •Location: Same file as tests
- •Use for: One-off fixtures not reused elsewhere
Step 3: Implement Fixture
For Basic Fixtures:
import pytest
from unittest.mock import MagicMock
@pytest.fixture
def fixture_name():
"""Clear docstring explaining purpose."""
# Setup
obj = MagicMock()
obj.attribute = "value"
# Return (no yield for simple fixtures)
return obj
For Factory Fixtures:
@pytest.fixture
def fixture_factory():
"""Factory for custom fixture."""
def create_fixture(**kwargs):
"""Create fixture with custom attributes.
Args:
**kwargs: Attributes to customize
Returns:
MagicMock: Configured fixture
"""
obj = MagicMock()
obj.attribute = kwargs.get("attribute", "default_value")
return obj
return create_fixture
For Async Fixtures:
import pytest_asyncio
from collections.abc import AsyncGenerator
@pytest_asyncio.fixture(scope="function")
async def async_fixture() -> AsyncGenerator[Resource, None]:
"""Async fixture with setup and teardown."""
# Setup
resource = await create_resource()
yield resource
# Teardown
await resource.close()
For Scoped Fixtures:
@pytest.fixture(scope="session")
def session_fixture():
"""Session-scoped fixture created once."""
# Expensive setup
resource = expensive_setup()
yield resource
# Cleanup after all tests
resource.cleanup()
Step 4: Add to Conftest Hierarchy
In tests/utils/mock_*.py:
"""Reusable mock fixtures for [component] components."""
from unittest.mock import MagicMock
import pytest
@pytest.fixture
def fixture_name():
"""Fixture docstring."""
return MagicMock()
In tests/conftest.py:
# Import to make available project-wide
from tests.utils.mock_settings import (
fixture_name,
)
__all__ = [
"fixture_name",
]
In layer-specific conftest:
# tests/unit/conftest.py
import pytest
@pytest.fixture
def unit_specific_fixture():
"""Fixture only for unit tests."""
return MagicMock()
Step 5: Use Fixtures in Tests
Basic usage:
def test_something(fixture_name):
"""Test using fixture."""
assert fixture_name.attribute == "value"
Factory usage:
def test_with_factory(fixture_factory):
"""Test using factory fixture."""
custom = fixture_factory(attribute="custom_value")
assert custom.attribute == "custom_value"
Async usage:
@pytest.mark.asyncio
async def test_async(async_fixture):
"""Test using async fixture."""
result = await async_fixture.operation()
assert result == expected
Multiple fixtures:
def test_with_multiple(fixture_one, fixture_two, fixture_factory):
"""Test using multiple fixtures."""
custom = fixture_factory(value="test")
assert fixture_one.works_with(fixture_two, custom)
Examples
Example 1: Basic Mock Fixture
import pytest
from unittest.mock import MagicMock
@pytest.fixture
def mock_settings_minimal():
"""Create minimal mock settings for basic testing."""
settings = MagicMock()
settings.project = MagicMock()
settings.project.project_name = "test_project"
settings.neo4j = MagicMock()
settings.neo4j.database_name = "test_db"
return settings
Example 2: Factory Fixture
@pytest.fixture
def mock_settings_factory():
"""Factory for custom mock settings."""
def create_settings(**kwargs):
settings = MagicMock()
settings.project = MagicMock()
settings.project.project_name = kwargs.get("project_name", "test_project")
settings.neo4j = MagicMock()
settings.neo4j.database_name = kwargs.get("database_name", "test_db")
settings.chunking = MagicMock()
settings.chunking.chunk_size = kwargs.get("chunk_size", 50)
return settings
return create_settings
Example 3: Async Fixture with Cleanup
import pytest_asyncio
from collections.abc import AsyncGenerator
from neo4j import AsyncGraphDatabase, AsyncDriver
@pytest_asyncio.fixture(scope="function")
async def real_neo4j_driver() -> AsyncGenerator[AsyncDriver, None]:
"""Create a real Neo4j driver with cleanup."""
driver = AsyncGraphDatabase.driver(
"bolt://localhost:7687",
auth=("neo4j", "password")
)
# Setup: Clear test data
async with driver.session(database="test_db") as session:
await session.run("MATCH (n {project_name: 'test_project'}) DETACH DELETE n")
yield driver
# Teardown: Close driver
await driver.close()
Script Files:
- •scripts/generate_fixture.py - Auto-generate pytest fixtures following project patterns
- •scripts/analyze_fixture_usage.py - Analyze pytest fixture usage and find optimization opportunities
- •scripts/organize_fixtures.py - Reorganize fixtures by layer and update imports
Requirements
- •pytest installed:
uv pip install pytest - •pytest-asyncio for async fixtures:
uv pip install pytest-asyncio - •Understanding of project structure:
- •
tests/utils/- Centralized reusable fixtures - •
tests/conftest.py- Project-wide fixture imports - •
tests/unit/conftest.py- Unit test specific fixtures - •
tests/integration/conftest.py- Integration test specific fixtures - •
tests/e2e/conftest.py- E2E test specific fixtures
- •
Fixture Best Practices
- •Naming: Use descriptive names with
mock_prefix for mocks - •Docstrings: Always document what the fixture provides
- •Scope: Default to
functionscope, usesession/moduleonly when needed - •Cleanup: Use
yieldfor fixtures that need teardown - •Factory Pattern: Return callable for customizable fixtures
- •Type Hints: Add return type hints for better IDE support
- •Reusability: Place reusable fixtures in
tests/utils/, import inconftest.py - •Layer Isolation: Keep layer-specific fixtures in layer conftest files
Common Patterns
Settings Mock:
@pytest.fixture
def mock_settings():
"""Standard settings mock."""
settings = MagicMock()
settings.project = MagicMock()
settings.neo4j = MagicMock()
return settings
Service Result Mock:
@pytest.fixture
def mock_service_result():
"""Factory for ServiceResult mocks."""
def create_result(success=True, data=None, error=None):
result = MagicMock()
result.is_success = success
result.data = data
result.error = error
return result
return create_result
Async Service Mock:
@pytest.fixture
def mock_embedding_service():
"""Async embedding service mock."""
service = AsyncMock()
service.generate_embeddings = AsyncMock(
return_value=MagicMock(success=True, data=[[0.1] * 384])
)
return service
Troubleshooting
Fixture not found:
- •Check it's imported in
tests/conftest.py - •Verify file is in Python path
- •Ensure
__all__includes fixture name
Async fixture errors:
- •Install
pytest-asyncio:uv pip install pytest-asyncio - •Use
@pytest_asyncio.fixturenot@pytest.fixture - •Mark test with
@pytest.mark.asyncio
Scope issues:
- •Session fixtures can't use function fixtures
- •Use broader scope for dependent fixtures
- •Consider fixture dependencies carefully
Cleanup not running:
- •Use
yieldnotreturn - •Ensure async cleanup uses
await - •Check for exceptions during test
Automation Scripts
NEW: Powerful automation utilities for fixture management:
scripts/generate_fixture.py
Auto-generate fixtures with proper patterns:
python .claude/skills/setup-pytest-fixtures/scripts/generate_fixture.py \ --name mock_service --type factory --attributes host,port,timeout
scripts/analyze_fixture_usage.py
Analyze fixture usage and find optimization opportunities:
python .claude/skills/setup-pytest-fixtures/scripts/analyze_fixture_usage.py --unused
scripts/organize_fixtures.py
Reorganize fixtures by layer and update imports:
python .claude/skills/setup-pytest-fixtures/scripts/organize_fixtures.py --validate
See Also
- •templates/fixture-templates.md - Copy-paste templates
- •
tests/utils/mock_settings.py- Settings fixture reference - •
tests/utils/mock_services.py- Service fixture reference - •
tests/utils/mock_drivers.py- Driver fixture reference - •
tests/utils/environmental_helpers.py- Environment fixture reference