Testing your app¶
TestClient drives your Application in-process — no real socket, no
running server, no extra dependency (it's stdlib-only, same philosophy as the
rest of the core). It speaks the exact ASGI protocol uvicorn does, just over
in-memory queues instead of a network connection.
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"}
Requests¶
get / post / put / patch / delete / head / options all share the
same keyword arguments:
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": "..."})
The returned TestResponse has .status_code, .headers, .content
(bytes), .text, .json(), and .cookies (parsed from Set-Cookie).
Streaming responses are supported transparently — every chunk is collected
into .content before the response comes back.
Lifespan: on_startup / on_shutdown¶
A bare TestClient(app) call is enough for stateless request testing. To
actually run your app's startup/shutdown hooks (opening a DB pool,
warming a cache, closing connections) — the same way a real deployment
does — use it as a context manager:
with TestClient(app) as client:
resp = client.post("/v1/user", json={"name": "Ada"})
...
# on_shutdown hooks already ran by the time the `with` block exits
A hook that raises during startup surfaces as an exception from __enter__,
so a broken on_startup fails your test loudly instead of silently skipping
setup.
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 performs the real accept handshake — if the server
rejects the connection (unknown route, failed origin check), entering the
with block raises WebSocketDisconnectError with the close code (4404,
4403, ...) instead of silently returning a dead session.
What this replaces¶
Before TestClient, testing a handler meant hand-building an ASGI scope
dict and fake receive/send callables (this is still how the framework's
own test suite drives edge cases, and remains a perfectly valid low-level
escape hatch — see tests/conftest.py's call() helper for the pattern).
TestClient exists so your own app's tests don't have to.