Cheatsheet for testing. Long-form: Textbook Ch 10 .
Setup
uv add --dev pytest pytest-anyio httpx pytest-cov factory_boy testcontainers respx freezegun
conftest.py (async client)
import pytest
from httpx import AsyncClient, ASGITransport
from app.main import app
@pytest.fixture(scope="session")
def anyio_backend(): return "asyncio"
@pytest.fixture
async def client():
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as c:
yield c
Basic test
import pytest
@pytest.mark.anyio
async def test_root(client):
r = await client.get("/")
assert r.status_code == 200
Dependency overrides
def fake_db(): return FakeDB()
@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://t") as c:
yield c
app.dependency_overrides.clear()
Real DB via testcontainers
from testcontainers.postgres import PostgresContainer
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
@pytest.fixture(scope="session")
def postgres():
with PostgresContainer("postgres:17") as pg:
yield pg
@pytest.fixture(scope="session")
async def engine(postgres):
url = postgres.get_connection_url().replace("psycopg2", "asyncpg")
e = create_async_engine(url)
async with e.begin() as c:
await c.run_sync(Base.metadata.create_all)
yield e
await e.dispose()
@pytest.fixture
async def db_session(engine):
conn = await engine.connect()
tx = await conn.begin()
async with AsyncSession(bind=conn, expire_on_commit=False) as s:
yield s
await tx.rollback()
await conn.close()
Per-test transaction rolls back on teardown. Fast + isolated.
factory_boy
import factory
class UserFactory(factory.Factory):
class Meta:
model = User
email = factory.Sequence(lambda n: f"u{n}@example.com")
full_name = factory.Faker("name")
is_active = True
# In a test
async def test_create(db_session):
u = UserFactory.build()
db_session.add(u)
await db_session.commit()
assert u.id
Auth client
@pytest.fixture
async def auth_client(client, db_session):
user = UserFactory.build(); db_session.add(user); await db_session.commit()
token = make_test_token(user.id)
client.headers["Authorization"] = f"Bearer {token}"
yield client
# Or via override
@pytest.fixture
def auth_client(client):
user = User(id=1, email="t@x", role="admin")
app.dependency_overrides[current_user] = lambda: user
yield client
app.dependency_overrides.pop(current_user, None)
Mock external HTTP (respx)
import respx
@pytest.fixture
def mock_external():
with respx.mock(base_url="https://upstream.api") as m:
m.get("/users/1").respond(json={"id": 1})
yield m
async def test_proxy(client, mock_external):
r = await client.get("/proxy/users/1")
assert mock_external.calls.call_count == 1
Freeze time
from freezegun import freeze_time
@freeze_time("2026-01-01")
async def test_today(client):
r = await client.get("/today")
assert r.json()["d"] == "2026-01-01"
Parametrize
@pytest.mark.parametrize("payload, code", [
({"email": "[email protected]", "name": "x"}, 201),
({"email": "bad"}, 422),
({}, 422),
])
async def test_validation(client, payload, code):
r = await client.post("/users", json=payload)
assert r.status_code == code
WebSocket test
from fastapi.testclient import TestClient
def test_ws():
with TestClient(app).websocket_connect("/ws") as ws:
ws.send_json({"hello": "world"})
assert ws.receive_json() == {"echo": {"hello": "world"}}
OpenAPI snapshot test
def test_openapi():
s = app.openapi()
assert "/users" in s["paths"]
assert "post" in s["paths"]["/users"]
Settings override (env)
@pytest.fixture(autouse=True)
def settings(monkeypatch):
monkeypatch.setenv("MYAPP_DATABASE_URL", "postgresql://...")
monkeypatch.setenv("MYAPP_SECRET_KEY", "test")
Coverage
pytest --cov=src --cov-report=term --cov-report=html --cov-fail-under=80
CI (GitHub Actions)
- uses: actions/setup-python@v5
with: { python-version: "3.13" }
- run: uv sync --frozen
- run: uv run pytest --cov=src --cov-fail-under=80
services:
postgres:
image: postgres:17
env: { POSTGRES_PASSWORD: test }
ports: ["5432:5432"]
options: --health-cmd "pg_isready"
Read this next
If you want my FastAPI test harness (testcontainers + factories + asyncclient), it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .