skills/dawiddutoit/custom-claude/setup-pytest-fixtures

setup-pytest-fixtures

SKILL.md

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:

  1. Basic Fixture: Simple, reusable test data

    • Use for: Constant values, simple mocks
    • Example: mock_settings_minimal
  2. Factory Fixture: Customizable with parameters

    • Use for: Configurable test data, parameterized tests
    • Example: mock_settings_factory(**kwargs)
  3. Async Fixture: For async operations

    • Use for: Async setup/teardown, async resources
    • Example: @pytest_asyncio.fixture async def...
  4. 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.py to 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:

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

  1. Naming: Use descriptive names with mock_ prefix for mocks
  2. Docstrings: Always document what the fixture provides
  3. Scope: Default to function scope, use session/module only when needed
  4. Cleanup: Use yield for fixtures that need teardown
  5. Factory Pattern: Return callable for customizable fixtures
  6. Type Hints: Add return type hints for better IDE support
  7. Reusability: Place reusable fixtures in tests/utils/, import in conftest.py
  8. 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.fixture not @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 yield not return
  • 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
Weekly Installs
3
First Seen
Feb 24, 2026
Installed on
mcpjam3
gemini-cli3
claude-code3
junie3
windsurf3
zencoder3