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 fixturestests/conftest.py- Project-wide fixture importstests/unit/conftest.py- Unit test specific fixturestests/integration/conftest.py- Integration test specific fixturestests/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 referencetests/utils/mock_services.py- Service fixture referencetests/utils/mock_drivers.py- Driver fixture referencetests/utils/environmental_helpers.py- Environment fixture reference