FastAPI Intermediate: Structure, Dependencies and Data
Testing Your API
In this lesson you will
- Write pytest tests using TestClient
- Isolate tests with a fixture and dependency override
- Test both success and error responses
Introduction
Tests prove that your API still works after you change it. With FastAPI you can call your app directly in Python, with no real server. The tests below were run with pytest and passed (4 passed).
Install: pip install pytest httpx. The test client needs the httpx package. Newer versions of the libraries may print a notice recommending a different HTTP client package for the test client. If you see it, follow the message.
TestClient
Python
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_health():
response = client.get("/health")
assert response.status_code == 200
assert response.json() == {"status": "ok"}Run with pytest. Files named test_*.py and functions named test_* are found automatically. Use plain assert statements.
Isolating the database
If tests use your real database file, they will interfere with each other and with real data. Instead, use a fixture that creates a fresh in-memory database and overrides the session dependency.
Python
import pytest
from fastapi.testclient import TestClient
from sqlalchemy.pool import StaticPool
from sqlmodel import Session, SQLModel, create_engine
from app.db import get_session
from app.main import app
@pytest.fixture(name="client")
def client_fixture():
engine = create_engine(
"sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool
)
SQLModel.metadata.create_all(engine)
def override_session():
with Session(engine) as session:
yield session
app.dependency_overrides[get_session] = override_session
yield TestClient(app)
app.dependency_overrides.clear()"sqlite://"is an in-memory database that disappears after the test.StaticPoolmakes all connections share that single in-memory database, which is necessary because each new connection tosqlite://would otherwise be an empty database.- The override replaces
get_sessionfor the test, and is cleared afterwards. - Each test that asks for
clientgets a new empty database, so tests do not depend on each other.
Note that creating TestClient(app) without a with block does not run the lifespan start-up, so the real library.db file is not created during tests.
The tests
Python
def test_create_and_get_book(client):
created = client.post("/books", json={"title": "Dune", "year": 1965})
assert created.status_code == 201
book_id = created.json()["id"]
fetched = client.get(f"/books/{book_id}")
assert fetched.status_code == 200
assert fetched.json()["title"] == "Dune"
def test_missing_book_is_404(client):
assert client.get("/books/999").status_code == 404
def test_rejects_bad_year(client):
response = client.post("/books", json={"title": "Old", "year": 1000})
assert response.status_code == 422Results:
.... [100%]
4 passed in 2.73sQuick quiz
Why override the session dependency in tests?
What to test
- Happy paths: valid input gives the right status and body.
- Errors: missing records (404), invalid input (422), forbidden actions (403).
- Edges: empty lists, maximum limits, boundary values such as year 1450 and 1449.
- Authentication: protected endpoints reject missing or bad tokens.
- Side effects: a delete really removes the row.
A good test is small, has a clear name, and checks one behaviour.
Habits
- Run tests often, and before every deploy.
- A failing test that you fix is a bug you caught early. Write a test for each bug you fix.
- Keep tests fast so you actually run them.
- Do not test the framework itself. Test your rules.
- Aim for useful coverage, not a perfect percentage.
Try it yourself
Add tests for deleting a book (204, then 404) and for listing with limit and offset. Make one test fail on purpose, read the report, then fix it.
Summary
- TestClient calls your app in Python, and pytest finds and runs the tests.
- A fixture with an in-memory database and dependency override isolates each test.
- Test success, error and edge cases, and run the tests regularly.
Lesson complete
+20 XP
Nice work. Keep going while it is fresh.
Sources and further reading