Testing
New Routa projects ship with Vitest, a test script, and a starter test beside the
generated /status route. This guide covers the three levels worth testing and where
Routa’s own checks already cover you.
Before You Start
Section titled “Before You Start”- A project created by
create-routa-ts, or Vitest installed and atestscript added. - A passing
routa check. Contract errors are cheaper to find there than in a test run.
-
Run the existing tests.
Terminal window npm run testTerminal window pnpm run testTerminal window yarn run testTerminal window bun run test -
Test a schema directly when you want to pin the shape of a payload. This is what the generated starter test does.
src/routes/status/route.test.ts import { describe, expect, it } from "vitest";import { GetStatusResponse } from "./schemas.js";describe("GET /status", () => {it("accepts the documented success response", () => {expect(GetStatusResponse.safeParse({ ok: true }).success).toBe(true);});}); -
Test a handler by calling its
rundirectly. It is a plain function, so passinputandctxand assert on the outcome name.src/routes/users/$userId/route.test.ts import { createLogger } from "@routa-ts/core/logger";import { describe, expect, it } from "vitest";import route from "./route.js";const ctx = { logger: createLogger({ enabled: false }) };describe("GET /users/:userId", () => {it("returns notFound for an unknown user", async () => {const result = await route.get.run({ input: { params: { userId: "nope" } }, ctx });expect(result.type).toBe("notFound");});}); -
Test the full boundary when middleware, parsing, and serialization all matter. Build the Hono app from your routes and use its
fetch.src/routes/users/route.http.test.ts import { createHonoApp } from "@routa-ts/core/hono";import { describe, expect, it } from "vitest";import route from "./route.js";const app = createHonoApp([{ method: "post", path: "/users", contract: route.post }]);describe("POST /users", () => {it("rejects a body that fails validation", async () => {const response = await app.request("/users", {method: "POST",headers: { "content-type": "application/json" },body: JSON.stringify({ name: "" }),});expect(response.status).toBe(400);});});
Verify
Section titled “Verify” ✓ src/routes/status/route.test.ts (1 test) 3ms
Test Files 1 passed (1) Tests 1 passed (1)What to Test Where
Section titled “What to Test Where”| Level | Use it for | Do not use it for |
|---|---|---|
| Schema | Payload shapes, coercion, optionality | Anything requiring a request |
| Handler | Outcome selection and service orchestration | Validation and status mapping, which Routa owns |
| HTTP | Middleware chains, parsing, status codes, content negotiation | Business rules better tested in services |
Business logic belongs in application-owned services and should be tested there, without Routa in the picture at all. A route test that needs a database is usually testing the wrong layer.
What You Do Not Need to Test
Section titled “What You Do Not Need to Test”Routa’s own checks already cover these, so tests asserting them only duplicate the framework:
- That an undeclared response type is rejected —
routa checkreportsROUTA_RESULT_TYPE. - That every method declares a 2xx response —
ROUTA_MISSING_SUCCESS_RESPONSE. - That middleware ordering satisfies
requires—ROUTA_MIDDLEWARE_ORDER. - That the OpenAPI document matches the source —
routa openapi check.
Run those as part of the same pipeline:
npm run checknpm run testnpm run openapi:checkpnpm run checkpnpm run testpnpm run openapi:checkyarn run checkyarn run testyarn run openapi:checkbun run checkbun run testbun run openapi:checkTroubleshooting
Section titled “Troubleshooting”| Problem | Fix |
|---|---|
Imports fail with ERR_MODULE_NOT_FOUND |
Use .js extensions in relative imports. Generated projects are ESM. |
A handler test cannot construct ctx |
Pass the keys the route’s middleware provides, plus a logger. createLogger({ enabled: false }) keeps test output clean. |
Type errors on route.get.run |
Metadata is stale. Run routa generate. |
Vitest picks up files under .routa/ |
Generated metadata contains no tests; narrow include in the Vitest config if a glob is too broad. |