> [Agent navigation index](/llms.txt)

# Testing routes and server behavior

Use Brix's shared route harness to test API, page, and fragment routes through production registration, middleware, rendering, environment parsing, and error handling without starting a server.

## Create the application harness

Generated and adopted applications include `src/test/harness.ts`. Keep `createApp` for normal application tests so every request receives a fresh production-configured app:

```ts
import { createTestHarness } from "brix/test";
import { HTTPException } from "brix/hono/http-exception";
import { createBrixApp } from "brix/server";
import { brixServer } from "#brix/server";

type Setup = { user?: true | { id: string; role: string }; tenant?: string };

export const { testApi, testFragment, testPage, withOptions } = createTestHarness<Setup>({
  createApp: () => createBrixApp(brixServer),
  env: { STAGE: "test", VERSION: "test" },
  setupContext: async ({ user, tenant }) => ({
    state: {
      ...(user ? { user: user === true ? { id: "user-1", role: "member" } : user } : {}),
      ...(tenant ? { tenant } : {}),
    },
    log: { info: () => {}, warn: () => {}, error: () => {} },
    httpException: (status: number) => new HTTPException(status as 500),
  }),
});

export const authenticated = withOptions({ user: true });

```

`setupContext` runs once before configured and route middleware for each request and may asynchronously create request-scoped users, tenants, or services. It supplements production middleware rather than replacing it. Harness-level `middleware` is available for test-only cross-cutting setup. Omit `createApp` only for an intentionally isolated library route.

The `env` snapshot is installed before production middleware and validated by the production environment parser. It never falls back to process globals, and invalid fixtures fail closed. This is the normal way to test request environment behavior; reserve `brix test --env` and `--stage` for integration tests that genuinely need configured providers.

## Test an API route

```ts
import { expect, it } from "brix/test";
import { authenticated, testApi } from "#brix/test";
import { updateProfileApiRoute } from "./route.ts";

it("rejects anonymous requests", async () => {
  const response = await testApi(updateProfileApiRoute, {
    params: { profileId: "profile-123" },
    body: { displayName: "Ada" },
  });
  expect(response.status).toBe(401);
  expect(response.error).toEqual({ error: "Unauthorized" });
});

it("updates an authenticated profile", async () => {
  const response = await authenticated.testApi(updateProfileApiRoute, {
    params: { profileId: "profile-123" },
    body: { displayName: "Ada" },
  });
  expect(response.status).toBe(200);
  expect(response.expectSuccess().displayName).toBe("Ada");
});

```

The route retains its contract. Required bodies and exact path params are enforced by TypeScript; body and query inputs are parsed as production handlers expect. Options also include `headers`, `cookies`, `locale`, and request-local `translations`. Additional setup keys are passed to `setupContext`.

`testApi` returns `status`, typed successful `data`, parsed `error`, unmodified `text`, `headers`, and a clone-safe `raw` response. `expectSuccess()` throws with the status for non-2xx responses and otherwise returns the non-optional typed payload. Assert the status and error payload directly when testing application error mapping or thrown `HTTPException`s.

## Test a rendered page

```ts
const response = await testPage(productDetailsPageRoute, {
  locale: "sv",
  params: { productId: "sku-123" },
  query: { preview: true },
  user: true,
});

expect(response.status).toBe(200);
expect(response.html).toContain("sku-123");
expect(response.headers.get("content-language")).toBe("sv");

```

The harness constructs the URL from the page contract, preserving locale, params, query serialization, route validation, global middleware, security headers, renderer, and server error handling. To test an opted-in semantic representation, pass `representation: "markdown"` and assert `response.markdown`; the response type prevents reading HTML from a Markdown result.

## Test fragments and composition

Test fragment endpoint loading and transport directly:

```ts
const endpoint = await testFragment(BookingWidgetFragment, {
  params: { hotelId: "hotel-1" },
  pageQuery: { campaign: "summer" },
});

expect(endpoint.status).toBe(200);
expect(endpoint.html).toContain("hotel-1");

```

Test a page with real in-process fragment routes, or replace selected edge responses with typed deterministic mocks:

```ts
import { mockFragment } from "brix/test";

const response = await testPage(bookingPageRoute, {
  query: { campaign: "summer" },
  fragments: [
    mockFragment(
      BookingWidgetFragment,
      ({ params, pageQuery }) => `<aside data-mock>${params.hotelId}:${pageQuery.campaign}</aside>`,
    ),
  ],
});

```

Pass fragment route objects instead of mocks to exercise their real loaders. `testPage` sends the production defer header, renders placeholders, forwards declared page query, and runs the production composer. A mock receives only public endpoint params and forwarded query—not server-loaded props. Missing registrations, invalid transport, endpoint failures, and composition errors fail the test. Keep `mockFragment` in Node tests; it is not a browser component mock.

## Reuse authenticated and service scenarios

`withOptions` binds setup values, headers, cookies, translations, or fragments without mutable globals:

```ts
const tenantAdmin = withOptions({
  user: { id: "admin-1", role: "admin" },
  tenant: "acme",
  headers: { "x-test-suite": "tenant-admin" },
});

const response = await tenantAdmin.testApi(updateProfileApiRoute, {
  params: { profileId: "profile-123" },
  body: { displayName: "Ada" },
});

```

Per-request values override bound defaults. Headers merge case-insensitively and cookies by case-sensitive name. Contract-required bodies and params remain required. Bound harnesses expose `withOptions` again, so suites can derive progressively specialized auth, tenant, and request-service scenarios.

Use these tests for Brix application infrastructure. If the subject merely consumes an HTTP API, use [contract-aware MSW mocks](/docs/testing-components#mock-http-consumers-with-msw) instead.
