Claude-skill-registry FastAPI_Pytest_TDDHelper
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/fastapi-pytest-tddhelper" ~/.claude/skills/majiayu000-claude-skill-registry-fastapi-pytest-tddhelper && rm -rf "$T"
manifest:
skills/data/fastapi-pytest-tddhelper/SKILL.mdsource content
FastAPI Pytest TDD Helper
High-performance TDD blueprint for FastAPI projects.
Core Principles
| Principle | Implementation |
|---|---|
| Speed | AsyncClient over TestClient (~20% faster) |
| Isolation | Transaction rollback, not schema recreation |
| TDD | Red-Green-Refactor cycle strictly |
| Validation | Pydantic models, not just status codes |
Quick Start
1. Install Dependencies
pip install pytest pytest-asyncio httpx aiosqlite pytest-cov
2. Configure pytest (pyproject.toml)
[tool.pytest.ini_options] asyncio_mode = "auto" testpaths = ["tests"] addopts = ["-v", "--tb=short", "-x"]
3. Create conftest.py
import pytest from httpx import AsyncClient, ASGITransport from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker from sqlalchemy.pool import NullPool from app.main import app from app.database import Base, get_db @pytest.fixture(scope="session") async def async_engine(): engine = create_async_engine("sqlite+aiosqlite:///./test.db", poolclass=NullPool) async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) yield engine async with engine.begin() as conn: await conn.run_sync(Base.metadata.drop_all) @pytest.fixture(scope="function") async def db_session(async_engine): async_session = async_sessionmaker(async_engine, class_=AsyncSession) async with async_session() as session: async with session.begin(): yield session await session.rollback() # Fast isolation! @pytest.fixture async def client(db_session): app.dependency_overrides[get_db] = lambda: db_session async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: yield ac app.dependency_overrides.clear()
TDD Workflow: Red-Green-Refactor
Step 1: RED - Write Failing Test
from pydantic import BaseModel class ItemResponse(BaseModel): id: int name: str price: float async def test_create_item(client): response = await client.post("/items/", json={"name": "Widget", "price": 10.0}) assert response.status_code == 201 item = ItemResponse(**response.json()) # Validate shape! assert item.name == "Widget"
Run:
pytest -x (fails - endpoint doesn't exist)
Step 2: GREEN - Minimal Implementation
@app.post("/items/", status_code=201, response_model=ItemResponse) async def create_item(item: ItemCreate, db: Session = Depends(get_db)): db_item = Item(**item.model_dump()) db.add(db_item) db.commit() return db_item
Run:
pytest -x (passes)
Step 3: REFACTOR - Optimize
Improve code quality, run tests to verify nothing breaks.
Reference Documentation
| Task | Reference |
|---|---|
| conftest.py patterns, fixture scopes | references/conftest-patterns.md |
| Red-Green-Refactor examples | references/tdd-workflow.md |
| pyproject.toml, parallel execution | references/pytest-optimization.md |
| Response validation with Pydantic | references/pydantic-validation.md |
| CRUD tests, mocking, overrides | references/testing-patterns.md |
Assets
| Template | Description |
|---|---|
| assets/conftest_template.py | Complete conftest.py ready to customize |
| assets/pyproject_template.toml | Optimized pytest configuration |
Performance Decisions
Why AsyncClient Over TestClient
# AVOID: Sync-to-async bridge overhead from fastapi.testclient import TestClient client = TestClient(app) # USE: Native async, ~20% faster from httpx import AsyncClient, ASGITransport async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: response = await client.get("/")
Why Transaction Rollback Over Schema Recreation
| Approach | 100 tests | 1000 tests |
|---|---|---|
| Schema recreation | ~60s | ~600s |
| Transaction rollback | ~5s | ~50s |
# FAST: Rollback at end of each test async with session.begin(): yield session await session.rollback()
Fixture Scoping Strategy
| Scope | Use For | Example |
|---|---|---|
| Expensive setup | DB engine, app instance |
| Test isolation | DB session with rollback |
Common Commands
# Run all tests pytest # Run with coverage pytest --cov=app --cov-report=term-missing # Run specific test pytest tests/test_items.py::test_create_item -v # Run excluding slow tests pytest -m "not slow" # Parallel execution pytest -n auto # Stop on first failure (TDD mode) pytest -x # Run failed tests first pytest --ff
Parametrize Pattern
@pytest.mark.parametrize("name,price,status", [ ("Valid", 10.0, 201), ("", 10.0, 422), # Empty name ("Item", -5.0, 422), # Negative price ]) async def test_create_item_validation(client, name, price, status): response = await client.post("/items/", json={"name": name, "price": price}) assert response.status_code == status
Factory Fixture Pattern
@pytest.fixture def item_factory(db_session): async def _create(name="Item", price=10.0, **kwargs): item = Item(name=name, price=price, **kwargs) db_session.add(item) await db_session.flush() return item return _create async def test_get_item(client, item_factory): item = await item_factory(name="Widget") response = await client.get(f"/items/{item.id}") assert response.json()["name"] == "Widget"