← Back to FastAPI map
FastAPI · Advanced

Testing

TestClient for quick tests, httpx for async ones, and swapping a dependency for a fake.

pytest

Overview

A FastAPI app is a plain ASGI object, so tests can call it in-process without starting a server. TestClient sends real HTTP requests through the whole stack - middleware, dependencies, validation - synchronously. For async tests, httpx.AsyncClient with ASGITransport does the same inside an event loop. dependency_overrides swaps the real database or auth for test versions.

Key concepts

TestClient
Synchronous and simple. Use it as with TestClient(app) as client: to also run the lifespan startup and shutdown.
ASGITransport
Routes httpx requests straight into the app. Required for AsyncClient since httpx 0.28.
dependency_overrides
A dict mapping each original dependency to its replacement; it applies to every route that uses that dependency.
Test database
Point get_db at a separate database, and roll back or recreate it between tests so they do not affect each other.

Best practices

  • Test through HTTP - status codes and JSON bodies are the contract clients rely on.
  • Clear dependency_overrides after each test, ideally in a pytest fixture, so tests stay independent.
  • Cover the error paths too: 401, 403, 404, and 422 responses are part of the API.

Testing FastAPI

TestClient - sync tests

Testing
# pip install httpx pytest from fastapi.testclient import TestClient from main import app client = TestClient(app) def test_read_root(): res = client.get("/") assert res.status_code == 200 assert res.json() == {"message": "Hello!"} def test_create_item(): res = client.post("/items", json={"name": "Foo", "price": 9.99}) assert res.status_code == 201 assert res.json()["name"] == "Foo" def test_not_found(): res = client.get("/items/99999") assert res.status_code == 404 def test_auth_header(): res = client.get("/me", headers={"Authorization": "Bearer fake-token"}) assert res.status_code == 401

Async tests + DB override

Async tests
# pip install pytest-asyncio import pytest from httpx import AsyncClient, ASGITransport from main import app from database import get_db # Override DB for tests async def get_test_db(): async with TestSessionLocal() as session: yield session app.dependency_overrides[get_db] = get_test_db @pytest.mark.asyncio async def test_create_user(): async with AsyncClient( transport=ASGITransport(app=app), base_url="http://test" ) as client: res = await client.post("/users", json={ "email": "test@rte.dev", "password": "secure123" }) assert res.status_code == 201 assert "password" not in res.json()

Watch out: Older examples use AsyncClient(app=app, ...). That shortcut was removed in httpx 0.28 - pass transport=ASGITransport(app=app).

Override auth in tests

Mocking
def override_get_current_user(): return User(id=1, username="testuser", role="admin") app.dependency_overrides[get_current_user] = override_get_current_user def test_me(): res = client.get("/me") # no token needed - dependency replaced assert res.json()["username"] == "testuser" # Reset after tests so overrides do not leak between them app.dependency_overrides.clear()

Comments

Sign in to leave a comment. Your name and photo come from Google; nothing else is shared.

Loading comments...