Перейти к содержанию

Тестирование приложения

TestClient прогоняет ваше Application in-process — без реального сокета, без запущенного сервера, без дополнительной зависимости (он полностью на стандартной библиотеке — та же философия, что и у всего ядра). Он говорит на том же самом ASGI-протоколе, что и uvicorn, просто через очереди в памяти вместо сетевого соединения.

from endocore import TestClient
from endocore.core.application import Application

app = Application(app_dir=".")
client = TestClient(app)

resp = client.get("/v1/user/42")
assert resp.status_code == 200
assert resp.json() == {"id": "42"}

Запросы

get / post / put / patch / delete / head / options принимают одинаковый набор именованных аргументов:

client.get("/v1/search", params={"q": "ada"})
client.post("/v1/user", json={"name": "Ada"})
client.post("/v1/login", data={"user": "ada", "pass": "x"})   # form-encoded
client.get("/v1/me", headers={"Authorization": "Bearer ..."})
client.get("/v1/me", cookies={"session": "..."})

Возвращаемый TestResponse имеет .status_code, .headers, .content (bytes), .text, .json() и .cookies (распарсенные из Set-Cookie). Стриминговые ответы поддерживаются прозрачно — все чанки собираются в .content до того, как ответ вернётся вызывающему коду.

Lifespan: on_startup / on_shutdown

Обычный вызов TestClient(app) достаточен для тестирования без состояния. А чтобы реально прогнать хуки on_startup/on_shutdown приложения (открытие пула БД, прогрев кэша, закрытие соединений) — так же, как это происходит в реальном деплое — используйте клиент как контекстный менеджер:

with TestClient(app) as client:
    resp = client.post("/v1/user", json={"name": "Ada"})
    ...
# к моменту выхода из блока `with` хуки on_shutdown уже отработали

Если хук падает во время старта, исключение всплывает прямо из __enter__ — сломанный on_startup роняет тест громко, а не молча пропускает настройку.

WebSockets

with TestClient(app) as client:
    with client.websocket_connect("/v1/chat") as ws:
        ws.send_text("hello")
        assert ws.receive_text() == "echo: hello"
        ws.send_json({"type": "ping"})
        assert ws.receive_json() == {"type": "pong"}

websocket_connect выполняет настоящий handshake с accept — если сервер отклоняет соединение (неизвестный маршрут, не прошла проверка origin), вход в блок with бросает WebSocketDisconnectError с кодом закрытия (4404, 4403, ...), а не молча возвращает мёртвую сессию.

Что это заменяет

До TestClient протестировать хендлер означало вручную собрать ASGI-словарь scope и фейковые receive/send (собственный тестовый набор фреймворка до сих пор так гоняет граничные случаи, и это остаётся вполне рабочим низкоуровневым запасным путём — см. хелпер call() в tests/conftest.py как образец паттерна). TestClient существует для того, чтобы тесты вашего приложения не нуждались в этом.