conftest.py.template 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. """
  2. Pytest configuration and fixtures.
  3. This is a template conftest.py with common patterns.
  4. Copy to tests/conftest.py and customize for your project.
  5. """
  6. import pytest
  7. from typing import Generator, Any
  8. from pathlib import Path
  9. # ============================================================
  10. # Pytest Hooks
  11. # ============================================================
  12. def pytest_configure(config):
  13. """Configure pytest with custom markers."""
  14. config.addinivalue_line("markers", "slow: marks tests as slow")
  15. config.addinivalue_line("markers", "integration: marks integration tests")
  16. config.addinivalue_line("markers", "e2e: marks end-to-end tests")
  17. def pytest_addoption(parser):
  18. """Add custom CLI options."""
  19. parser.addoption(
  20. "--slow",
  21. action="store_true",
  22. default=False,
  23. help="Run slow tests",
  24. )
  25. parser.addoption(
  26. "--integration",
  27. action="store_true",
  28. default=False,
  29. help="Run integration tests",
  30. )
  31. def pytest_collection_modifyitems(config, items):
  32. """Modify test collection based on options."""
  33. # Skip slow tests unless --slow
  34. if not config.getoption("--slow"):
  35. skip_slow = pytest.mark.skip(reason="use --slow to run")
  36. for item in items:
  37. if "slow" in item.keywords:
  38. item.add_marker(skip_slow)
  39. # Skip integration tests unless --integration
  40. if not config.getoption("--integration"):
  41. skip_integration = pytest.mark.skip(reason="use --integration to run")
  42. for item in items:
  43. if "integration" in item.keywords:
  44. item.add_marker(skip_integration)
  45. # ============================================================
  46. # Session-Scoped Fixtures
  47. # ============================================================
  48. @pytest.fixture(scope="session")
  49. def project_root() -> Path:
  50. """Return project root directory."""
  51. return Path(__file__).parent.parent
  52. @pytest.fixture(scope="session")
  53. def test_data_dir(project_root: Path) -> Path:
  54. """Return test data directory."""
  55. return project_root / "tests" / "data"
  56. # ============================================================
  57. # Common Fixtures
  58. # ============================================================
  59. @pytest.fixture
  60. def sample_dict() -> dict[str, Any]:
  61. """Provide sample dictionary for testing."""
  62. return {
  63. "id": 1,
  64. "name": "Test Item",
  65. "active": True,
  66. "tags": ["a", "b", "c"],
  67. }
  68. @pytest.fixture
  69. def temp_file(tmp_path: Path) -> Generator[Path, None, None]:
  70. """Create temporary file with cleanup."""
  71. file_path = tmp_path / "test_file.txt"
  72. file_path.write_text("test content")
  73. yield file_path
  74. # Cleanup happens automatically via tmp_path
  75. @pytest.fixture
  76. def mock_env(monkeypatch) -> Generator[dict[str, str], None, None]:
  77. """Set up mock environment variables."""
  78. env_vars = {
  79. "TEST_MODE": "true",
  80. "API_KEY": "test-key-12345",
  81. "DATABASE_URL": "sqlite:///:memory:",
  82. }
  83. for key, value in env_vars.items():
  84. monkeypatch.setenv(key, value)
  85. yield env_vars
  86. # ============================================================
  87. # Factory Fixtures
  88. # ============================================================
  89. @pytest.fixture
  90. def user_factory():
  91. """Factory to create user objects with custom attributes."""
  92. def _create_user(
  93. id: int = 1,
  94. name: str = "Test User",
  95. email: str = "test@example.com",
  96. active: bool = True,
  97. ) -> dict[str, Any]:
  98. return {
  99. "id": id,
  100. "name": name,
  101. "email": email,
  102. "active": active,
  103. }
  104. return _create_user
  105. # ============================================================
  106. # Database Fixtures (uncomment if using SQLAlchemy)
  107. # ============================================================
  108. # from sqlalchemy import create_engine
  109. # from sqlalchemy.orm import sessionmaker, Session
  110. # from myapp.database import Base
  111. #
  112. # @pytest.fixture(scope="session")
  113. # def db_engine():
  114. # """Create test database engine."""
  115. # engine = create_engine(
  116. # "sqlite:///:memory:",
  117. # echo=False,
  118. # )
  119. # Base.metadata.create_all(engine)
  120. # yield engine
  121. # engine.dispose()
  122. #
  123. # @pytest.fixture
  124. # def db_session(db_engine) -> Generator[Session, None, None]:
  125. # """Create database session with rollback."""
  126. # Session = sessionmaker(bind=db_engine)
  127. # session = Session()
  128. # yield session
  129. # session.rollback()
  130. # session.close()
  131. # ============================================================
  132. # API Fixtures (uncomment if using FastAPI)
  133. # ============================================================
  134. # from fastapi.testclient import TestClient
  135. # from myapp import create_app
  136. #
  137. # @pytest.fixture
  138. # def app():
  139. # """Create test application."""
  140. # return create_app(testing=True)
  141. #
  142. # @pytest.fixture
  143. # def client(app) -> Generator[TestClient, None, None]:
  144. # """Create test client."""
  145. # with TestClient(app) as client:
  146. # yield client
  147. #
  148. # @pytest.fixture
  149. # def auth_client(client: TestClient) -> TestClient:
  150. # """Create authenticated test client."""
  151. # client.headers["Authorization"] = "Bearer test-token"
  152. # return client
  153. # ============================================================
  154. # Async Fixtures (uncomment if using pytest-asyncio)
  155. # ============================================================
  156. # import asyncio
  157. # import aiohttp
  158. #
  159. # @pytest.fixture(scope="session")
  160. # def event_loop():
  161. # """Create event loop for async tests."""
  162. # loop = asyncio.new_event_loop()
  163. # yield loop
  164. # loop.close()
  165. #
  166. # @pytest.fixture
  167. # async def async_client():
  168. # """Async HTTP client."""
  169. # async with aiohttp.ClientSession() as session:
  170. # yield session