Skip to content

Ensuring Quality: Testing

Reliability is the cornerstone of professional software engineering. Even the fastest API is useless if it returns incorrect data or crashes unexpectedly. In this chapter, we will explore how to write automated tests for your FastAPI application, ensuring that your code behaves exactly as expected before it ever reaches production.

FastAPI stands on the shoulders of giants, and for testing, it leverages the power of Starlette. We will use TestClient, a tool that allows you to simulate HTTP requests to your application without actually running a server. It interacts directly with your FastAPI app object.

For the test runner, the industry standard in the Python ecosystem is pytest. It is simple to use yet incredibly powerful.

First, you need to install pytest and httpx. TestClient uses httpx under the hood to perform requests.

Terminal window
pip install pytest httpx

Let’s assume you have a simple file main.py.

main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"msg": "Hello World"}

To test this, create a file named test_main.py. In this file, we import TestClient and our app.

test_main.py
from fastapi.testclient import TestClient
from .main import app
client = TestClient(app)
def test_read_root():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"msg": "Hello World"}

To run the test, simply execute pytest in your terminal. pytest automatically detects files starting with test_ and functions starting with test_.

Testing isn’t just about the “happy path” (when everything goes right). It is equally important to ensure your API fails gracefully when given invalid input. Since FastAPI automatically validates data using Pydantic, we can test that these validation errors occur as expected.

main.py
@app.get("/items/{item_id}")
def read_item(item_id: int):
return {"item_id": item_id}
# test_main.py
def test_read_item_bad_type():
# We send a string "foo", but the API expects an integer
response = client.get("/items/foo")
assert response.status_code == 422
# We can even check the specific error message structure
assert response.json()["detail"][0]["msg"] == "Input should be a valid integer"

Isolating Environments: Overriding Dependencies

Section titled “Isolating Environments: Overriding Dependencies”

One of the most powerful features of FastAPI’s testing system is the ability to override dependencies. This is crucial when your application interacts with external resources like a database, an authentication provider, or an email service.

You usually do not want your tests to modify your production database or send real emails. FastAPI allows you to swap out a real dependency with a “mock” or test version using app.dependency_overrides.

Imagine you have a dependency that verifies a token:

# main.py dependency
async def verify_token(x_token: str = Header(...)):
if x_token != "super-secret-token":
raise HTTPException(status_code=400, detail="Invalid X-Token header")
return x_token

In your test, you can bypass the logic of checking the specific string by overriding it.

test_main.py
from fastapi.testclient import TestClient
from .main import app, verify_token
client = TestClient(app)
async def override_verify_token():
# We simply return a value, bypassing the check
return "test-token"
# Apply the override
app.dependency_overrides[verify_token] = override_verify_token
def test_secure_endpoint():
response = client.get("/secure-data")
assert response.status_code == 200
# Clean up the override after the test so other tests aren't affected
app.dependency_overrides = {}

This technique is extensively used to replace a real SQL database session with a temporary, in-memory SQLite database for testing purposes, ensuring your tests are fast, isolated, and safe.