""" Pytest configuration and fixtures. This is a template conftest.py with common patterns. Copy to tests/conftest.py and customize for your project. """ import pytest from typing import Generator, Any from pathlib import Path # ============================================================ # Pytest Hooks # ============================================================ def pytest_configure(config): """Configure pytest with custom markers.""" config.addinivalue_line("markers", "slow: marks tests as slow") config.addinivalue_line("markers", "integration: marks integration tests") config.addinivalue_line("markers", "e2e: marks end-to-end tests") def pytest_addoption(parser): """Add custom CLI options.""" parser.addoption( "--slow", action="store_true", default=False, help="Run slow tests", ) parser.addoption( "--integration", action="store_true", default=False, help="Run integration tests", ) def pytest_collection_modifyitems(config, items): """Modify test collection based on options.""" # Skip slow tests unless --slow if not config.getoption("--slow"): skip_slow = pytest.mark.skip(reason="use --slow to run") for item in items: if "slow" in item.keywords: item.add_marker(skip_slow) # Skip integration tests unless --integration if not config.getoption("--integration"): skip_integration = pytest.mark.skip(reason="use --integration to run") for item in items: if "integration" in item.keywords: item.add_marker(skip_integration) # ============================================================ # Session-Scoped Fixtures # ============================================================ @pytest.fixture(scope="session") def project_root() -> Path: """Return project root directory.""" return Path(__file__).parent.parent @pytest.fixture(scope="session") def test_data_dir(project_root: Path) -> Path: """Return test data directory.""" return project_root / "tests" / "data" # ============================================================ # Common Fixtures # ============================================================ @pytest.fixture def sample_dict() -> dict[str, Any]: """Provide sample dictionary for testing.""" return { "id": 1, "name": "Test Item", "active": True, "tags": ["a", "b", "c"], } @pytest.fixture def temp_file(tmp_path: Path) -> Generator[Path, None, None]: """Create temporary file with cleanup.""" file_path = tmp_path / "test_file.txt" file_path.write_text("test content") yield file_path # Cleanup happens automatically via tmp_path @pytest.fixture def mock_env(monkeypatch) -> Generator[dict[str, str], None, None]: """Set up mock environment variables.""" env_vars = { "TEST_MODE": "true", "API_KEY": "test-key-12345", "DATABASE_URL": "sqlite:///:memory:", } for key, value in env_vars.items(): monkeypatch.setenv(key, value) yield env_vars # ============================================================ # Factory Fixtures # ============================================================ @pytest.fixture def user_factory(): """Factory to create user objects with custom attributes.""" def _create_user( id: int = 1, name: str = "Test User", email: str = "test@example.com", active: bool = True, ) -> dict[str, Any]: return { "id": id, "name": name, "email": email, "active": active, } return _create_user # ============================================================ # Database Fixtures (uncomment if using SQLAlchemy) # ============================================================ # from sqlalchemy import create_engine # from sqlalchemy.orm import sessionmaker, Session # from myapp.database import Base # # @pytest.fixture(scope="session") # def db_engine(): # """Create test database engine.""" # engine = create_engine( # "sqlite:///:memory:", # echo=False, # ) # Base.metadata.create_all(engine) # yield engine # engine.dispose() # # @pytest.fixture # def db_session(db_engine) -> Generator[Session, None, None]: # """Create database session with rollback.""" # Session = sessionmaker(bind=db_engine) # session = Session() # yield session # session.rollback() # session.close() # ============================================================ # API Fixtures (uncomment if using FastAPI) # ============================================================ # from fastapi.testclient import TestClient # from myapp import create_app # # @pytest.fixture # def app(): # """Create test application.""" # return create_app(testing=True) # # @pytest.fixture # def client(app) -> Generator[TestClient, None, None]: # """Create test client.""" # with TestClient(app) as client: # yield client # # @pytest.fixture # def auth_client(client: TestClient) -> TestClient: # """Create authenticated test client.""" # client.headers["Authorization"] = "Bearer test-token" # return client # ============================================================ # Async Fixtures (uncomment if using pytest-asyncio) # ============================================================ # import asyncio # import aiohttp # # @pytest.fixture(scope="session") # def event_loop(): # """Create event loop for async tests.""" # loop = asyncio.new_event_loop() # yield loop # loop.close() # # @pytest.fixture # async def async_client(): # """Async HTTP client.""" # async with aiohttp.ClientSession() as session: # yield session