This version is in beta. Some features may change before release.

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 an axum::Router with 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.
Info
Add `umbral-testing` under `[dev-dependencies]`. It's a test-only utility crate, never shipped in a release build.

A request round-trip

Code
rust
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.

Code
rust
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:

MethodChecks
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_fixture in crates/umbral-core/src/fixtures.rs.
testingtest-clientintegration