Skip to content

Testing

The in-memory test clients.

TestClient

Sync test client - drives the app through its ASGI surface.

Usage::

client = TestClient(app)
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "ok"}

cookies property

cookies: _TestClientCookies

Live view of the client's cookie jar.

Supports dict-like access (client.cookies["session"]), assignment (client.cookies["k"] = "v"), deletion, iteration, and bulk clear(). Cookies the server sends on responses are automatically merged in via _update_cookies. The state persists across calls until the client is closed.

set_cookie(key: str, value: str) -> None

Add or update a cookie sent on every subsequent request.

delete_cookie(key: str) -> None

Remove a cookie from the jar. No-op if not present.

wait_for_background_tasks

wait_for_background_tasks(timeout: float | None = 5.0) -> bool

Wait for the app's background tasks to finish, on this client's loop.

A response's background task runs after the response is returned, so a test asserting its effect otherwise has to reach for client._loop and drive it with a hand-picked number of turns. Returns True when everything finished, False on timeout.

Usage::

client.post("/subscribe")
client.wait_for_background_tasks()
assert sent == ["welcome@example.com"]

session_transaction

session_transaction() -> Iterator[Session]

Mutate the session outside a request.

Yields a Session dict pre-loaded from the current session cookie (if any). On block exit the session is re-signed with the app's SessionMiddleware secret and stored in the cookie jar, so the next request carries it::

with client.session_transaction() as sess:
    sess["user_id"] = 7

Raises RuntimeError if the app has no SessionMiddleware.

get

get(path: str, headers: dict[str, str] | None = None, params: dict[str, str] | Sequence[tuple[str, str]] | None = None, follow_redirects: bool | None = None) -> TestResponse

Send a GET request and return the response.

post

post(path: str, json: Any = None, data: dict[str, str] | None = None, headers: dict[str, str] | None = None, content: bytes | None = None, files: dict[str, Any] | None = None, follow_redirects: bool | None = None, stream: StreamBody | None = None) -> TestResponse

Send a POST.

stream feeds the body as multiple http.request chunks (a sync Iterable or AsyncIterable of bytes/str); when given it takes precedence over and excludes json/data/content/files.

put

put(path: str, json: Any = None, data: dict[str, str] | None = None, headers: dict[str, str] | None = None, content: bytes | None = None, files: dict[str, Any] | None = None, follow_redirects: bool | None = None, stream: StreamBody | None = None) -> TestResponse

Send a PUT. See post for the stream chunked-body parameter.

patch

patch(path: str, json: Any = None, data: dict[str, str] | None = None, headers: dict[str, str] | None = None, content: bytes | None = None, files: dict[str, Any] | None = None, follow_redirects: bool | None = None, stream: StreamBody | None = None) -> TestResponse

Send a PATCH. See post for the stream chunked-body parameter.

delete

delete(path: str, headers: dict[str, str] | None = None, follow_redirects: bool | None = None) -> TestResponse

Send a DELETE request and return the response.

head

head(path: str, headers: dict[str, str] | None = None, follow_redirects: bool | None = None) -> TestResponse

Send a HEAD request and return the response.

options

options(path: str, headers: dict[str, str] | None = None, follow_redirects: bool | None = None) -> TestResponse

Send an OPTIONS request and return the response.

request

request(method: str, path: str, json: Any = None, data: dict[str, str] | None = None, headers: dict[str, str] | None = None, content: bytes | None = None, files: dict[str, Any] | None = None, params: dict[str, str] | Sequence[tuple[str, str]] | None = None, follow_redirects: bool | None = None, stream: StreamBody | None = None) -> TestResponse

Dispatch a request of any verb - httpx/test-client shape.

client.request("PATCH", "/x", json=...) is the verb-agnostic form of client.get / client.post / .... Bodies (json / data / content / files / stream) and params are handled exactly as the per-verb methods do; stream (see post) excludes the buffered body forms.

websocket_connect

websocket_connect(path: str, subprotocols: list[str] | None = None, headers: dict[str, str] | None = None) -> _WebSocketSession

Open an in-memory WebSocket against the app - context manager.

Drives the ASGI websocket protocol: synthesise the scope, send websocket.connect, route to the handler, then forward send_text / receive_text / close calls to the running handler through a pair of asyncio queues.

close

close() -> None

Run shutdown lifecycle and close the loop if we own it.

AsyncTestClient

Async in-memory test client - drives the app through its ASGI surface.

The async counterpart of TestClient: used as an async context manager inside an async test, so each request is awaited on the test's own running event loop instead of through a private loop. The request methods (get / post / ...) are coroutines.

Usage::

async with AsyncTestClient(app) as client:
    resp = await client.get("/")

Cookie persistence, redirect following, and the JSON / form / files body shapes match TestClient exactly. WebSocket testing stays on the sync TestClient.websocket_connect, and seeding a session outside a request stays on the sync TestClient.session_transaction.

cookies property

cookies: _TestClientCookies

Live view of the client's cookie jar (see TestClient.cookies).

wait_for_background_tasks async

wait_for_background_tasks(timeout: float | None = 5.0) -> bool

Wait for the app's background tasks to finish. See TestClient.

set_cookie(key: str, value: str) -> None

Add or update a cookie sent on every subsequent request.

delete_cookie(key: str) -> None

Remove a cookie from the jar. No-op if not present.

get async

get(path: str, headers: dict[str, str] | None = None, params: dict[str, str] | Sequence[tuple[str, str]] | None = None, follow_redirects: bool | None = None) -> TestResponse

Send a GET request and return the response.

post async

post(path: str, json: Any = None, data: dict[str, str] | None = None, headers: dict[str, str] | None = None, content: bytes | None = None, files: dict[str, Any] | None = None, follow_redirects: bool | None = None, stream: StreamBody | None = None) -> TestResponse

Send a POST.

stream feeds the body as multiple http.request chunks (a sync Iterable or AsyncIterable of bytes/str); when given it takes precedence over and excludes json/data/content/files.

put async

put(path: str, json: Any = None, data: dict[str, str] | None = None, headers: dict[str, str] | None = None, content: bytes | None = None, files: dict[str, Any] | None = None, follow_redirects: bool | None = None, stream: StreamBody | None = None) -> TestResponse

Send a PUT. See post for the stream chunked-body parameter.

patch async

patch(path: str, json: Any = None, data: dict[str, str] | None = None, headers: dict[str, str] | None = None, content: bytes | None = None, files: dict[str, Any] | None = None, follow_redirects: bool | None = None, stream: StreamBody | None = None) -> TestResponse

Send a PATCH. See post for the stream chunked-body parameter.

delete async

delete(path: str, headers: dict[str, str] | None = None, follow_redirects: bool | None = None) -> TestResponse

Send a DELETE request and return the response.

head async

head(path: str, headers: dict[str, str] | None = None, follow_redirects: bool | None = None) -> TestResponse

Send a HEAD request and return the response.

options async

options(path: str, headers: dict[str, str] | None = None, follow_redirects: bool | None = None) -> TestResponse

Send an OPTIONS request and return the response.

request async

request(method: str, path: str, json: Any = None, data: dict[str, str] | None = None, headers: dict[str, str] | None = None, content: bytes | None = None, files: dict[str, Any] | None = None, params: dict[str, str] | Sequence[tuple[str, str]] | None = None, follow_redirects: bool | None = None, stream: StreamBody | None = None) -> TestResponse

Dispatch a request of any verb (see TestClient.request).

TestResponse

What every TestClient / AsyncTestClient call returns.

A read-only view of one ASGI response cycle: status_code, the decoded text and parsed json(), a case-insensitive headers mapping, the parsed cookies, and raw_headers for the cases the mapping flattens (several Set-Cookie lines arrive as one joined value in headers, and separately in raw_headers).

Not constructed directly - the clients build it.

Usage::

with TestClient(app) as client:
    response = client.get("/items/1")

assert response.status_code == 200
assert response.json() == {"id": 1}
assert response.headers["content-type"].startswith("application/json")

text property

text: str

Decode the response body as UTF-8 text.

json

json() -> Any

Parse the response body as JSON and return the result.