| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- """
- 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
|