Unit 09.00: Testing the whole request path in-process
TestClient exercises the whole request path in-process, with no server and no network.
Everything except the network
Routing, validation, dependency resolution, the handler, the response model and error handling - all in a function call.
The code posts a valid and an invalid request.
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field
app = FastAPI()
class Request(BaseModel):
text: str = Field(min_length=1, max_length=40)
@app.post("/classify")
def classify(body: Request) -> dict:
return {"category": "billing", "confidence": 0.9}
client = TestClient(app)
r = client.post("/classify", json={"text": "charged twice"})
print(f"status {r.status_code}, body {r.json()}")
print(f"invalid -> {client.post('/classify', json={'text': ''}).status_code}")
print("""
`TestClient` runs routing, validation, dependency resolution, the handler, the
response model and error handling -- in-process, with no server, no port and
no network.
That is most of what breaks in an API, exercised in milliseconds.
""")
That covers most of what actually breaks in an API. Validation rules, status codes, response filtering and dependency wiring are all exercised, in milliseconds.
There is no port to bind, nothing to start and nothing to tear down, which is what makes it fast enough to run on every save.
The mistake this prevents
The mistake is testing by starting the server and calling it with an HTTP client. It is slower, it needs a free port, it fails in CI for reasons unrelated to your code, and it tests nothing extra.
Takeaway
TestClient runs the full request path in-process. It covers routing, validation, dependencies, response models and error handling without a network.
