Testing Web APIs
API tests hit your application in-process without starting a server. FastAPI's TestClient, Flask's test client, and Django's Client send requests and assert on responses.
Recipe
from fastapi.testclient import TestClient
from myapp.main import app
client = TestClient(app)
def test_health():
response = client.get("/health")
assert response.status_code == 200
assert response.json() == {"status": "ok"}When to reach for this:
- Verifying route handlers, status codes, and response bodies
- Testing auth middleware and error responses
- CI tests that must not depend on external services
Working Example
# tests/conftest.py
import pytest
from fastapi.testclient import TestClient
from myapp.main import app
@pytest.fixture
def client():
return TestClient(app)
@pytest.fixture
def auth_client(client):
client.headers.update({"Authorization": "Bearer test-token"})
return clientdef test_create_invoice(client):
response = client.post("/invoices", json={"amount": 100.0, "currency": "USD"})
assert response.status_code == 201
data = response.json()
assert data["amount"] == 100.0
assert "id" in data
def test_unauthorized(auth_client):
response = auth_client.get("/admin/users")
assert response.status_code in (200, 403)
def test_validation_error(client):
response = client.post("/invoices", json={"amount": -1})
assert response.status_code == 422What this demonstrates:
TestClientruns the ASGI app in-process- JSON request/response testing with
.json() - Fixture for authenticated client
- Pydantic validation errors return 422
Deep Dive
Framework Clients
| Framework | Client | Import |
|---|---|---|
| FastAPI | TestClient | fastapi.testclient |
| Flask | test_client() | app.test_client() |
| Django | Client | django.test.Client |
| Any ASGI | httpx + transport | httpx.ASGITransport |
Async Testing
import httpx
from httpx import ASGITransport
@pytest.fixture
async def async_client():
transport = ASGITransport(app=app)
async with httpx.AsyncClient(transport=transport, base_url="http://test") as c:
yield cGotchas
- TestClient with async lifespan - startup events may not run. Fix: use
with TestClient(app) as client:context manager. - Shared mutable app state - tests affect each other. Fix: fixture-scoped app or dependency overrides per test.
- Testing against real DB - slow, flaky. Fix: override DB dependency with in-memory SQLite in tests.
- Not testing error paths - only 200 responses tested. Fix: assert 400, 401, 404, 422 responses explicitly.
- Hardcoded auth tokens in tests - drift from real auth. Fix: fixture that mirrors production auth dependency override.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| httpx against running server | E2E staging tests | Unit/route tests |
| Schemathesis | OpenAPI fuzzing | Simple CRUD routes |
| Postman/Newman | Manual exploration | Automated CI unit tests |
FAQs
TestClient or httpx?
TestClient for sync tests (simpler). httpx AsyncClient for async test suites.
How do I override FastAPI dependencies?
app.dependency_overrides[dep] = lambda: mock_value in fixture setup/teardown.
How do I test Flask apps?
client = app.test_client() then client.get("/path").
How do I test Django views?
pytest-django + client.get("/url/") with Django test Client.
Do I need a running server?
No. In-process clients are faster and deterministic.
How do I test WebSockets?
TestClient(app).websocket_connect("/ws") for FastAPI/Starlette.
How do I test file uploads?
client.post("/upload", files={"file": ("test.txt", b"content")}).
How do I test middleware?
Send requests that trigger middleware. Assert headers and status changes.
Should API tests use a real database?
Integration tests: yes (test DB). Unit route tests: override with mock or SQLite.
How do I test OpenAPI schema?
Assert /openapi.json structure or use Schemathesis for property-based API testing.
Related
- Testing Async Code - async httpx client
- Fixtures - client fixtures
- Mocking & Patching - override dependencies
- FastAPI Testing - FastAPI-specific patterns
Stack versions: This page was written for Python 3.14.0, FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.