Test client
Boot your app in a test, send requests, and assert on the response with TestClient, TempPool, and response assertions.
Test client
umbral-testing gives you ergonomic integration-test helpers: a test database plus an in-process HTTP client. The repeated work in every integration test (spin up a pool, build the router, send a request, read the response) collapses into three types:
TempPool: a tempfile-backed SQLite pool, deleted when the guard drops.TestClient: wraps anaxum::Routerwith HTTP-verb methods, a per-client cookie jar (a session set on one request rides on the next), and JSON helpers.TestResponse: owns the body and headers and exposes assertions.
A request round-trip
use umbral_testing::{TempPool, TestClient}; #[tokio::test]async fn list_endpoint_returns_seeded_rows() { let pool = TempPool::new().await; // ... build the router using pool.handle(), seed rows ... let client = TestClient::new(router); let resp = client.get("/api/notes").await; resp.assert_status_ok(); let notes: Vec<Note> = resp.body_json(); assert_eq!(notes.len(), 2);}Sending requests
TestClient has get(uri) and delete(uri) for bodyless verbs, post(uri, body) for a raw axum::body::Body, and post_json(uri, &value) / put_json(uri, &value) for a JSON body (they set Content-Type: application/json). For any other verb, use send(method, uri, body). The cookie jar is automatic: a Set-Cookie on one response is sent back on the next request, so a login flow just works.
client.post_json("/api/auth/login", &json!({ "username": "ada", "password": "secret" })).await .assert_status_ok(); // the session cookie rides automatically on this next call:client.get("/api/profile").await.assert_status_ok();Set a header that persists across requests (e.g. a bearer token) with set_default_header(name, value), and read a cookie the server set with cookie(name).
Asserting on the response
TestResponse carries the status, headers, and body, with chainable assertions:
| Method | Checks |
|---|---|
assert_status(code) / assert_status_ok() | the HTTP status (prints the body on mismatch) |
assert_body_contains(needle) | a substring of the rendered body |
assert_header(name, expected) | a response header value |
body_text() / body_bytes() | the raw body |
body_json::<T>() | deserialize the body into T (panics with the raw body on a parse error) |
status() / headers() / header(name) | read without asserting |
The assertion methods return &Self, so they chain.
See also
- Factories: generate realistic rows to drive these requests.
- JSON seed files:
load_fixture/dump_fixtureincrates/umbral-core/src/fixtures.rs.