Skip to content

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.