Testing FastAPI Applications

FastAPI is easy to test. Its TestClient calls your application in-process, without a running server, and dependency overrides let you replace databases, authentication, and external services with test versions in one line. Combined with pytest fixtures, you can write fast unit-style endpoint tests and realistic integration tests against a real database.

A good suite covers the HTTP contract (status codes, validation errors, response shapes), business logic, authorization rules, and database behavior, while staying fast enough to run on every commit.

TL;DR

Quick Example

Core Concepts

TestClient

fastapi.testclient.TestClient wraps the app with an httpx-compatible interface (client.get, post, put, and so on) that dispatches directly to the ASGI app. There's no network and no server, and it works with both sync and async endpoints. Using it as a context manager (with TestClient(app) as client) triggers the lifespan handler, so resources created at startup (DB engines, HTTP clients) exist during the test.

Async Tests

When tests themselves need to await (async fixtures, async DB sessions), use an async client:

ASGITransport doesn't run lifespan events by itself; use asgi-lifespan's LifespanManager, or set up resources in fixtures. Keep event loop scopes consistent between async fixtures and tests to avoid "attached to a different loop" errors, especially with async database drivers.

Dependency Overrides

app.dependency_overrides[original] = replacement replaces any dependency, including nested ones, for all requests until cleared. Typical overrides:

Always clear overrides in fixture teardown. See FastAPI dependencies.

Database Testing

See integration testing.

Testing Authentication and Authorization

Mocking External Services

Outbound HTTP calls should be intercepted:

Or inject a fake client via dependency override, which is often cleaner than patching. See mocking and stubbing.

Best Practices

Test the Contract, Not the Framework

Focus on your behavior: status codes, validation messages, response shapes, authorization, and side effects. There's no need to test that FastAPI parses JSON.

Keep Business Logic in Plain Functions

Service functions that take explicit arguments are testable without HTTP at all. Endpoint tests then cover wiring and HTTP concerns, and service tests cover the rules.

Parametrize Permission Matrices

Use pytest.mark.parametrize over roles, scopes, and expected statuses to cover authorization systematically in few lines.

Run the Suite in CI With Real Services

Run Testcontainers or service containers (PostgreSQL, Redis) in CI, and include alembic upgrade head in the pipeline. See CI/CD.

Common Mistakes

Leaking Overrides Between Tests

Setting app.dependency_overrides at module level and never clearing it makes tests order-dependent. Set and clear overrides inside fixtures.

Testing Against SQLite While Production Uses PostgreSQL

JSON operators, ON CONFLICT, timezone handling, and constraint behavior differ, so tests pass while production fails. Use the real engine.

Forgetting Lifespan

Creating TestClient(app) without a with block skips startup, so resources initialized in lifespan are missing, and tests fail with confusing AttributeErrors or None clients.

FAQ

Do I need async tests for async endpoints?

No. TestClient can call async endpoints from synchronous tests. Use async tests (httpx AsyncClient) only when the test itself needs to await things, like async fixtures or direct async database queries.

How do I test endpoints that require authentication?

Override the get_current_user dependency with a function returning a test user, adjusting scopes and roles per test. Keep a few tests that send real tokens through the full validation path to cover the security code itself.

How do I isolate database state between tests?

Wrap each test in a transaction that's rolled back afterwards (binding the session to an outer connection with savepoints), or truncate tables between tests. Transaction rollback is faster, and keeps tests independent.

How do I test background tasks?

TestClient runs BackgroundTasks after the response within the same call, so their effects are visible after the request returns. For real task queues (Celery, Arq, Dramatiq), run tasks eagerly in tests, or assert on enqueued jobs.

Related Topics

References