Тестирование FastAPI-приложений
FastAPI тестируют через TestClient (поверх httpx), который шлёт запросы приложению без реального сервера, а зависимости подменяют через app.dependency_overrides для изоляции от БД и внешних сервисов.
Тест в FastAPI — это обычный вызов эндпоинта в памяти:
TestClientделает «запрос» прямо к приложению и возвращает ответ, как настоящий клиент, но без сети и без uvicorn.
Тестируемость — следствие архитектуры FastAPI. Поскольку всё внешнее (БД, токены, сервисы) приходит через зависимости, в тесте их легко заменить на подделки. А TestClient позволяет вызывать эндпоинты как HTTP, не поднимая сервер: он гоняет запрос через всё приложение (валидацию, зависимости, обработчик) и отдаёт ответ для проверок.
from fastapi.testclient import TestClient
from main import app, get_db
client = TestClient(app)
def fake_db():
return {"users": {1: {"id": 1, "name": "Тест"}}}
app.dependency_overrides[get_db] = fake_db # подменяем реальную БД
def test_get_user():
response = client.get("/users/1")
assert response.status_code == 200
assert response.json()["name"] == "Тест"
def test_user_not_found():
response = client.get("/users/999")
assert response.status_code == 404
Ключевая техника — dependency_overrides: словарь, где реальной зависимости сопоставляется тестовая. Эндпоинт ничего не знает о подмене — он по-прежнему просит get_db, но получает фальшивую. Так тесты не ходят в настоящую базу, выполняются быстро и детерминированно. После теста подмену сбрасывают.
Как работает под капотом
Переопределение зависимостей — это просто подмена функции в словаре до разрешения графа. FastAPI при разрешении сперва смотрит в dependency_overrides. Смоделируем механизм на stdlib:
def get_db():
return "РЕАЛЬНАЯ база (медленно, с сетью)"
overrides = {}
def resolve(dep):
func = overrides.get(dep, dep) # сначала ищем подмену
return func()
def handler():
db = resolve(get_db)
return f"обработчик работает с: {db}"
print("без подмены:", handler())
# тест подменяет зависимость
overrides[get_db] = lambda: "ФЕЙКОВАЯ база (в памяти, быстро)"
print("в тесте: ", handler())
overrides.clear() # сброс после теста
print("после сброса:", handler())
Попробуй сам ▶ Один и тот же обработчик работает то с реальной, то с фейковой зависимостью — без единого изменения в его коде. Это и есть сила DI для тестов.
Частые ошибки
Первая — ходить в настоящую базу из тестов, делая их медленными и хрупкими; подменяйте сессию. Вторая — забыть очистить dependency_overrides между тестами, из-за чего подмена «протекает» в другие тесты. Третья — тестировать только успешные пути, игнорируя ошибки (404, 422, 401). Четвёртая — путать TestClient (синхронный, для большинства тестов) и асинхронный httpx-клиент (нужен для проверки настоящего async-поведения).
Best practices
- Подменяйте внешние зависимости (БД, токены) через
dependency_overridesради скорости и детерминизма. - Очищайте переопределения между тестами (например, в фикстуре pytest).
- Покрывайте и ошибочные ветки: 404, 422, 401, а не только успех.
- Используйте отдельную тестовую БД/транзакции с откатом, если нужна реальная интеграция.
Пирамида тестов и фикстуры pytest
Не все тесты одинаково ценны. Полезно держать в голове пирамиду: много быстрых модульных тестов на чистую логику (валидаторы, сервисы) в основании, меньше интеграционных тестов эндпоинтов через TestClient в середине, и совсем немного сквозных тестов с реальной базой наверху. TestClient-тесты с подменой зависимостей — золотая середина: они проверяют реальный конвейер (валидацию, зависимости, обработчик), оставаясь быстрыми. Организуют их через фикстуры pytest: одна фикстура создаёт клиента, другая настраивает и сбрасывает dependency_overrides, третья выдаёт тестовую сессию БД с откатом транзакции после каждого теста, чтобы тесты не влияли друг на друга. Такой подход даёт детерминированный, изолированный и быстрый набор тестов. Помните: тест без проверки ошибочных веток — половина теста; всегда проверяйте, что на плохой вход API отвечает 422, на отсутствие ресурса — 404, на чужой токен — 401.
Итог: TestClient гоняет запросы через всё приложение без сервера, а dependency_overrides подменяет внешние зависимости фейками. Архитектура на DI делает FastAPI тестируемым по своей природе — пользуйтесь этим и проверяйте не только счастливые пути.