How does TestClient work for testing FastAPI applications?
TestClient (from Starlette, re-exported by FastAPI) wraps httpx to make synchronous HTTP requests to your FastAPI app without starting a real server. It runs the ASGI app in-process.
How it works:
- Instantiate
TestClient(app)with your FastAPI application - Call
client.get(),client.post(), etc. like a real HTTP client - Responses have
.status_code,.json(),.text,.headers - No network I/O -- everything runs in-process
Key features:
- Automatic JSON serialization with
json=parameter - Cookie persistence across requests within the same client
- WebSocket testing via
client.websocket_connect() - Follows redirects by default (disable with
follow_redirects=False)
Requirement:
pip install httpx -- TestClient requires httpx as a dependency.
from fastapi.testclient import TestClient
client = TestClient(app)
response = client.get("/items")
assert response.status_code == 200
assert response.json() == [{"id": 1, "name": "item"}]
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
items_db: list[dict] = []
@app.get("/items")
def list_items():
return items_db
@app.post("/items", status_code=201)
def create_item(item: Item):
item_dict = item.model_dump()
item_dict["id"] = len(items_db) + 1
items_db.append(item_dict)
return item_dict
@app.get("/items/{item_id}")
def get_item(item_id: int):
for item in items_db:
if item["id"] == item_id:
return item
return {"error": "not found"}
# --- Tests ---
client = TestClient(app)
# Test GET empty list
response = client.get("/items")
print(f"GET /items: {response.status_code} {response.json()}")
# Test POST
response = client.post("/items", json={"name": "Widget", "price": 9.99})
print(f"POST /items: {response.status_code} {response.json()}")
# Test GET after creation
response = client.get("/items/1")
print(f"GET /items/1: {response.status_code} {response.json()}")
# Test validation error (missing required field)
response = client.post("/items", json={"name": "Widget"})
print(f"Missing price: {response.status_code}")
- •TestClient runs the ASGI app in-process with no real network I/O
- •Use json= for automatic serialization of request bodies
- •response.json() parses the response body; response.status_code for HTTP status
- •Requires httpx: pip install httpx
- •TestClient is synchronous in its interface but fully supports async def endpoints -- it runs the ASGI app internally via anyio, so all async code works correctly
- •items_db state persists between tests in the same module -- clear it in fixtures
- •TestClient follows redirects by default, which can mask redirect bugs
- •422 status means Pydantic validation failed -- check response.json() for details