Тестирование приложения¶
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 существует для того, чтобы тесты вашего
приложения не нуждались в этом.