Первый тест API через TestClient
Уроки курсаПервый тест API через TestClient
assert: статус и JSON существующей и отсутствующей комнаты
Создайте папку first_api_test и положите в неё два файла.
Первый — само маленькое приложение:
# demo_api.py
from fastapi import FastAPI, HTTPException
app = FastAPI()
rooms = {1: {"id": 1, "name": "Альфа", "capacity": 6}}
@app.get("/api/rooms/{room_id}")
def read_room(room_id: int):
room = rooms.get(room_id)
if room is None:
raise HTTPException(status_code=404, detail="Room not found")
return room
Словарь rooms задан явно и не меняется тестами. Маршрут возвращает карточку комнаты или поднимает HTTPException с кодом 404.
Второй файл — тесты:
# test_rooms.py
from fastapi.testclient import TestClient
from demo_api import app
def test_read_room():
with TestClient(app) as client:
response = client.get("/api/rooms/1")
assert response.status_code == 200
assert response.json() == {"id": 1, "name": "Альфа", "capacity": 6}
def test_missing_room():
with TestClient(app) as client:
response = client.get("/api/rooms/999")
assert response.status_code == 404
assert response.json() == {"detail": "Room not found"}
Запустите из папки first_api_test:
python -m pytest -q
Ожидаемый вывод:
2 passed in 0.XXs
response.status_code — это обычное целое число. response.json() разбирает тело ответа и возвращает соответствующий Python-объект: словарь, список, строку, число, bool или None. Обычный assert сравнивает их напрямую.
Проверка статуса 200 сама по себе недостаточна. Представьте, что обработчик вернул None вместо словаря — FastAPI сериализует это в null, статус останется 200, но assert response.json() == {"id": 1, ...} упадёт. Именно поэтому в test_read_room проверяются оба утверждения: статус и точное тело.
Когда assert не выполняется, pytest показывает само утверждение и фактическое значение — дополнительных assertion-библиотек для этого не нужно.
HTTPException является Python-исключением. Для показанного404 оно обрабатывается внутри приложения и не прерывает вызов TestClient ошибкой Python. После выхода из функции маршрута слой обработки исключений FastAPI/Starlette превращает HTTPException в Response с нужным статусом и телом {"detail": "..."}. Поэтому test_missing_room получает нормальный ответ и может проверить его содержимое.
Другая ситуация — если в обработчике возникает обычное Python-исключение, которое никто не поймал (например, ZeroDivisionError). При стандартных настройках TestClient такое исключение не превращается в ответ 500, а поднимается прямо в тесте. Тест упадёт с трейсбэком Python, а не с assert по статусу.
Для полноты: если маршрут отвечает 204 (No Content) и тело пустое, вызывать response.json() не стоит — там нечего разбирать. Проверяйте response.content == b"".
Два пройденных теста подтверждают два конкретных сценария: существующая комната возвращает точную карточку, несуществующая — 404 с понятным сообщением. Остальные маршруты и сценарии с записью в базу этими тестами не покрыты.
