FastAPI: From Basics to Production · Testing FastAPI Applications
Integration Tests with a Real Database
Run tests against PostgreSQL with migrations and per-test isolation.
Test against the same database engine as production (PostgreSQL via Docker, Testcontainers or a CI service). Apply Alembic migrations once per session, then isolate each test: run it inside a transaction that's rolled back (binding the session to a connection with an outer transaction), or truncate tables. Override the get_db dependency to use the test session.
import pytest
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from app.db import get_db
from app.main import app
engine = create_async_engine(TEST_DATABASE_URL)
@pytest.fixture
async def db():
async with engine.connect() as conn:
trans = await conn.begin()
session = AsyncSession(bind=conn, join_transaction_mode="create_savepoint", expire_on_commit=False)
app.dependency_overrides[get_db] = lambda: session
try:
yield session
finally:
await session.close()
await trans.rollback() # undo everything the test did
app.dependency_overrides.pop(get_db, None)
Common pitfalls
- Using SQLite to test PostgreSQL-specific SQL.
- Tests that commit for real and pollute later tests.
- Creating schemas with
create_allinstead of running migrations (migrations go untested).
Key points
- Same engine as production
- Rollback-per-test isolation
- Run real migrations
Further reading: SQLAlchemy: joining an external transaction
Practice questions
How can app code that calls commit() be rolled back in tests?
Answer
Bind the session to a connection with an outer transaction and use join_transaction_mode='create_savepoint'; app commits release savepoints while the outer transaction is rolled back after the test.
Why run Alembic migrations in tests instead of Base.metadata.create_all?
Answer
It verifies the migrations themselves and ensures the test schema matches production exactly.
Open in the app to mark lessons complete, take notes and track your progress.