Гайды
Тестирование FastAPI: pytest, TestClient и AsyncClient
httpx AsyncClient, фикстуры, dependency_overrides, БД в тестах, параметризация и покрытие в CI.
~10 мин чтения
Тестирование FastAPI: pytest, TestClient и AsyncClient
Тесты API на FastAPI строят на httpx (рекомендуемый AsyncClient) или классическом TestClient из Starlette. pytest даёт фикстуры для приложения и переиспользуемой БД. Каркас приложения — Быстрый старт с FastAPI.
1. Зависимости
pip install pytest pytest-asyncio httpx
В pytest.ini или pyproject.toml:
[pytest]
asyncio_mode = auto
2. Синхронный TestClient
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_health():
r = client.get("/health")
assert r.status_code == 200
assert r.json() == {"status": "ok"}
Подходит для sync эндпоинтов и простых случаев; внутри поднимает ASGI-сервер в потоке.
3. AsyncClient (предпочтительно для async)
import pytest
from httpx import ASGITransport, AsyncClient
from main import app
@pytest.fixture
async def ac():
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as c:
yield c
@pytest.mark.asyncio
async def test_items(ac: AsyncClient):
r = await ac.get("/items/1")
assert r.status_code == 200
Так вы избегаете смешивания async/sync в одном цикле событий.
4. Переопределение Depends (мок БД / пользователя)
from fastapi import Depends
async def fake_user():
return {"id": 1, "role": "admin"}
app.dependency_overrides[get_current_user] = fake_user
try:
r = client.get("/admin/stats")
finally:
app.dependency_overrides.clear()
Или в фикстуре yield — очищайте overrides после теста.
5. Фикстура БД
- SQLite in-memory + aiosqlite для интеграционных тестов.
- Или testcontainers с PostgreSQL для максимальной близости к продакшену.
Прокиньте URL БД через os.environ до импорта settings.
6. Параметризация
@pytest.mark.parametrize("item_id,status", [(1, 200), (-1, 404)])
def test_get_item(item_id, status):
r = client.get(f"/items/{item_id}")
assert r.status_code == status
7. Покрытие и CI
pytest -q --cov=app --cov-report=term-missing
В CI поднимайте только unit/integration без внешних сервисов или с контейнерами.
8. Что ещё тестировать
- 422 на невалидном теле (Pydantic).
- 401/403 на защищённых маршрутах — см. JWT и OAuth2.
- Заголовки
Locationдля 201/303.
9. Lifespan и фоновые задачи
Если приложение использует @asynccontextmanager lifespan (пулы БД, клиенты), поднимайте AsyncClient внутри тестовой фикстуры после инициализации lifespan или используйте LifespanManager из библиотеки asgi-lifespan, чтобы в тесте отработали те же startup/shutdown хуки, что в проде.
10. Фикстуры и изоляция
Держите TestClient/AsyncClient на scope function, чтобы dependency_overrides не протекали между тестами. Для тяжёлой БД — session-scoped контейнер Testcontainers + transaction rollback на каждый тест (быстрее, чем пересоздание схемы). Генерация данных — Factory Boy / faker вместо ручных JSON в десятках тестов.
11. WebSocket (smoke)
У TestClient есть контекстный менеджер websocket_connect — проверка рукопожатия и первого кадра без отдельного браузера:
with client.websocket_connect("/ws/notify") as ws:
ws.send_json({"type": "ping"})
data = ws.receive_json()
assert data.get("type") == "pong"
Для AsyncClient смотрите актуальный API httpx / обёртки; сложные сценарии часто выносят в e2e с реальным ASGI-сервером.
Дальше: Pydantic · тег pytest