Testing
The app.request() method lets you test routes without a running server. Pass a path and optional options, get a Response back.
Basic testing
import { describe, test, expect } from "vitest";
import { app } from "../server";
describe("GET /books", () => {
test("returns a list of books", async () => {
const res = await app.request("/books");
const body = await res.json();
expect(res.status).toBe(200);
expect(Array.isArray(body)).toBe(true);
});
}); No HTTP server is started. app.request processes the request through the full lifecycle: onRequest, route matching, validation, handler, onResponse.
Testing POST requests
test("creates a book", async () => {
const res = await app.request("/books", {
method: "POST",
body: {
title: "Kindred",
authorId: "author-1",
genre: "science-fiction",
},
});
expect(res.status).toBe(201);
const book = (await res.json()) as { title: string };
expect(book.title).toBe("Kindred");
}); When you pass a body, it is automatically serialized as JSON and the Content-Type header is set to application/json.
Testing with authentication
Include the appropriate headers in the options:
test("authenticated request", async () => {
const res = await app.request("/profile", {
headers: { Authorization: "Bearer valid-test-token" },
});
expect(res.status).toBe(200);
});
test("unauthenticated request", async () => {
const res = await app.request("/profile");
expect(res.status).toBe(401);
}); Testing validation errors
test("returns 400 for invalid body", async () => {
const res = await app.request("/books", {
method: "POST",
body: {}, // Missing required fields
});
expect(res.status).toBe(400);
const body = (await res.json()) as { error: unknown };
expect(body.error).toBeDefined();
}); Testing error handling
test("returns 404 for unknown book", async () => {
const res = await app.request("/books/nonexistent-id");
expect(res.status).toBe(404);
}); Testing response headers
test("includes request ID in response", async () => {
const res = await app.request("/books");
expect(res.headers.get("x-request-id")).toBeDefined();
});
test("includes CORS headers", async () => {
const res = await app.request("/books", {
headers: { Origin: "https://myapp.com" },
});
expect(res.headers.get("access-control-allow-origin")).toBe("https://myapp.com");
}); Testing with query parameters
test("filters by tag", async () => {
const res = await app.request("/books", {
headers: { Authorization: "Bearer alice" },
query: { tag: "fiction", limit: "10" },
});
expect(res.status).toBe(200);
}); RequestOptions
interface RequestOptions {
method?: string; // Default: "GET"
body?: unknown; // Automatically serialized as JSON
headers?: Record<string, string>;
query?: Record<string, string | string[]>;
}