Testing Overview
zzz ships a dedicated testing module that lets you exercise your application without starting a real server or opening network connections. The testing utilities cover three areas:
- HTTP test client — send requests through your router and assert on responses, headers, cookies, and JSON bodies.
- WebSocket test channel — join topics, push events, and inspect messages without a TCP connection.
- Database sandbox — wrap each test in a transaction that rolls back automatically, plus factories for creating test records.
All testing utilities live under zzz.testing and are designed to work with Zig’s built-in test blocks and std.testing.
What the testing module provides
Section titled “What the testing module provides”| Utility | Import | Purpose |
|---|---|---|
TestClient | zzz.testing.TestClient | HTTP request/response testing against your router |
TestResponse | zzz.testing.TestResponse | Response wrapper with assertion methods |
RequestBuilder | zzz.testing.RequestBuilder | Chainable builder for complex requests |
CookieJar | zzz.testing.CookieJar | Automatic cookie persistence across requests |
MultipartPart | zzz.testing.MultipartPart | Multipart/form-data body construction |
TestChannel | zzz.testing.TestChannel | WebSocket channel testing |
TestSandbox | zzz_db.testing.TestSandbox | Transaction-based database isolation |
Factory | zzz_db.testing.Factory | Test record creation with defaults and overrides |
Setting up tests
Section titled “Setting up tests”zzz tests run with zig build test. No external test runner or server process is needed — the test client calls your router’s handler function directly.
-
Define your router as you would in your application:
const zzz = @import("zzz");const Router = zzz.Router;const Context = zzz.Context;const App = Router.define(.{.routes = &.{Router.get("/", struct {fn handle(ctx: *Context) !void {ctx.text(.ok, "hello");}}.handle),},}); -
Create a test client parameterized on your
Apptype:const std = @import("std");test "homepage returns hello" {var client = zzz.testing.TestClient(App).init(std.testing.allocator);defer client.deinit();const resp = try client.get("/");try resp.expectOk();try resp.expectBody("hello");} -
Run the tests with
zig build testor directly withzig test src/your_file.zig.
How it works
Section titled “How it works”The TestClient is generic over the App type returned by Router.define(). When you call client.get("/"), the client:
- Constructs an HTTP
Requeststruct with the method, path, headers, and body you specified. - Merges in default headers and cookies from the built-in
CookieJar. - Calls
App.handler(allocator, &request)directly — no TCP, no event loop. - Wraps the resulting
Responsein aTestResponsethat provides assertion helpers. - Automatically follows redirects (configurable) and captures
Set-Cookieheaders.
This means your tests run at full speed with no I/O overhead, while exercising the same middleware pipeline and routing logic your application uses in production.
A complete example
Section titled “A complete example”const std = @import("std");const zzz = @import("zzz");const Router = zzz.Router;const Context = zzz.Context;
const App = Router.define(.{ .routes = &.{ Router.get("/", struct { fn handle(ctx: *Context) !void { ctx.text(.ok, "welcome"); } }.handle), Router.post("/api/login", struct { fn handle(ctx: *Context) !void { ctx.setCookie("session", "tok_abc", .{}); ctx.json(.ok, "{\"ok\":true}"); } }.handle), Router.get("/dashboard", struct { fn handle(ctx: *Context) !void { const session = ctx.getCookie("session") orelse { ctx.text(.unauthorized, "not logged in"); return; }; _ = session; ctx.text(.ok, "dashboard"); } }.handle), },});
test "full login flow" { var client = zzz.testing.TestClient(App).init(std.testing.allocator); defer client.deinit();
// Verify homepage const home = try client.get("/"); try home.expectOk(); try home.expectBody("welcome");
// Log in -- cookie is captured automatically const login = try client.postJson("/api/login", "{}"); try login.expectOk(); try login.expectCookieValue("session", "tok_abc");
// Access protected route -- cookie is sent automatically const dash = try client.get("/dashboard"); try dash.expectOk(); try dash.expectBody("dashboard");}Next steps
Section titled “Next steps”- HTTP Test Client — full API reference for
TestClient,RequestBuilder, andTestResponseassertions. - WebSocket Testing — testing channels with
TestChannel. - Database Sandbox — transaction-based test isolation, factories, and seeding.