FastAPI для начинающих: API с базой данных и тестамиТесты вместо ручного прокликиванияПервый тест API через TestClient

Первый тест 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 с понятным сообщением. Остальные маршруты и сценарии с записью в базу этими тестами не покрыты.