Getting Started with Testing
Kyrin comes with a built-in testing toolkit that lets you test your routes, middleware, and request hooks without spinning up a real HTTP server. Everything runs in-memory for fast, predictable tests.
Installation
The test utilities are built into Kyrin — no additional dependencies are needed.
typescript
import { createApp, TestApp, TestClient } from "kyrin";Your First Test
typescript
import { createApp, expectStatus, expectBody } from "kyrin";
const app = createApp();
app.get("/hello", () => ({ message: "Hello, World!" }));
const client = app.getClient();
const res = await client.get("/hello");
expectStatus(res.status).toBe(200);
expectBody(res.body).toEqual({ message: "Hello, World!" });TestApp
TestApp is a lightweight, in-process application that mimics Kyrin's production router and middleware system. Create one with createApp().
Creating an App
typescript
const app = createApp();Registering Routes
typescript
app.get("/users", () => ({ users: ["Alice", "Bob"] }));
app.get("/users/:id", (c) => ({ id: c.param("id") }));
app.post("/users", async (c) => {
const body = await c.body();
return { created: true, ...body };
});
app.put("/users/:id", (c) => ({ updated: c.param("id") }));
app.patch("/users/:id", (c) => ({ patched: c.param("id") }));
app.delete("/users/:id", () => ({ deleted: true }));
// Custom HTTP method
app.on("GET", "/custom", (c) => ({ method: "GET" }));
// Match any method
app.all("/fallback", (c) => ({ fallback: true }));Adding Middleware
typescript
app.use(async (c, next) => {
c.set.headers["X-Request-Id"] = "test-123";
await next();
});
// Multiple middlewares run in order
app.use(async (c, next) => {
console.log("Before handler");
await next();
console.log("After handler");
});Request Hooks
Request hooks let you intercept or modify requests before they reach your route handler.
typescript
app.onRequest((c) => {
// Modify the request before it's processed
c.req.headers.set("Authorization", "Bearer test-token");
});
// Return early without hitting the handler
app.onRequest((c) => {
if (c.path === "/blocked") {
return new Response("Blocked", { status: 403 });
}
});TestClient
TestClient provides convenience methods for making HTTP requests against your TestApp.
Creating a Client
typescript
const client = app.getClient();Making Requests
typescript
// GET
const res = await client.get("/users");
const res = await client.get("/users?page=1&limit=10");
// POST with body
const res = await client.post("/users", { name: "Alice" });
// POST with body and custom headers
const res = await client.post("/users", { name: "Alice" }, {
Authorization: "Bearer token123",
});
// PUT
const res = await client.put("/users/1", { name: "Bob" });
// PATCH
const res = await client.patch("/users/1", { name: "Bob" });
// DELETE
const res = await client.delete("/users/1");TestResponse
Every request returns a TestResponse with a predictable shape:
typescript
interface TestResponse {
status: number; // HTTP status code
headers: Record<string, string>; // Response headers
body: unknown; // Parsed response body
}
const res = await client.get("/users");
console.log(res.status); // 200
console.log(res.headers); // { "content-type": "application/json" }
console.log(res.body); // { users: ["Alice", "Bob"] }Complete Test Example
typescript
import { createApp, expectStatus, expectBody } from "kyrin";
describe("User API", () => {
let app: ReturnType<typeof createApp>;
let client: ReturnType<ReturnType<typeof createApp>["getClient"]>;
beforeEach(() => {
app = createApp();
// Middleware
app.use(async (c, next) => {
c.set.headers["X-Test"] = "true";
await next();
});
// Routes
app.get("/users", () => ({ users: ["Alice", "Bob"] }));
app.get("/users/:id", (c) => ({ id: c.param("id") }));
app.post("/users", async (c) => {
const body = await c.body();
return { created: true, ...body };
});
// Request hook
app.onRequest((c) => {
if (c.path === "/secret") {
return new Response("Forbidden", { status: 403 });
}
});
client = app.getClient();
});
test("GET /users returns user list", async () => {
const res = await client.get("/users");
expectStatus(res.status).toBe(200);
expectBody(res.body).toEqual({ users: ["Alice", "Bob"] });
});
test("GET /users/:id returns the id param", async () => {
const res = await client.get("/users/123");
expectStatus(res.status).toBe(200);
expectBody(res.body).toEqual({ id: "123" });
});
test("POST /users creates a user", async () => {
const res = await client.post("/users", { name: "NewUser" });
expectStatus(res.status).toBe(200);
expectBody(res.body).toEqual({ created: true, name: "NewUser" });
});
test("onRequest hook can block paths", async () => {
const res = await client.get("/secret");
expectStatus(res.status).toBe(403);
expectBody(res.body).toBe("Forbidden");
});
});Next Steps
- Expect Helpers — Assertions:
expect(),expectStatus(),expectBody() - Mocking Utilities — Spies, stubs, mock database, mock request/response