Testing

Scenario tests are colocated with routes as run.test.ts files. Each scenario declares an input state and the expected output, and dawn test runs them against the real route runtime.

A minimal test

A scenario file is a default-exported array of plain scenario records — there is no describe() or test() wrapper, and the route is inferred from the file's directory:

src/app/(public)/hello/[tenant]/run.test.ts
export default [
  {
    name: "greets a tenant",
    input: { tenant: "acme" },
    expect: {
      status: "passed",
      output: { tenant: "acme", greeting: "Hello, acme!" },
    },
  },
]

Run all scenarios:

text
dawn test

Dawn discovers every run.test.ts under src/app/, invokes the route for each scenario, and evaluates the declarative expectation.

Scenario shape

Each entry in the default-exported array is { name, input, expect, run?, assert? }:

  • name — human-readable scenario name.
  • input — the JSON state passed to the route.
  • expect — declarative expectation (see below).
  • run? — optional run config: { url?: string } to target a live dev server.
  • assert? — optional (result) => void callback for custom assertions, evaluated alongside expect.

The expect object accepts:

  • status (required) — "passed" | "failed".
  • output? — deep-equal match against the route's returned state.
  • meta? — match against { mode, routeId, routePath, executionSource }.
  • error? — for status: "failed", match { kind, message: string | { includes: string } }.

For programmatic assertions in assert(result), import the helpers from @dawn-ai/sdk/testing:

src/app/(public)/hello/[tenant]/run.test.ts
import { expectError, expectMeta, expectOutput } from "@dawn-ai/sdk/testing"
 
export default [
  {
    name: "custom assert",
    input: { tenant: "acme" },
    expect: { status: "passed" },
    assert: (result) => {
      expectOutput(result, { greeting: "Hello, acme!" })
      expectMeta(result, { mode: "agent", routeId: "/hello/[tenant]" })
    },
  },
]

Against a live dev server

To exercise protocol parity against a running dawn dev, set run.url per-scenario. There is no command-level --url flag on dawn test.

src/app/(public)/hello/[tenant]/run.test.ts
export default [
  {
    name: "greets a tenant via dev server",
    input: { tenant: "acme" },
    run: { url: "http://127.0.0.1:3001" },
    expect: {
      status: "passed",
      output: { tenant: "acme", greeting: "Hello, acme!" },
    },
  },
]

Start the dev server first, then run dawn test:

text
dawn dev --port 3001 &
dawn test

Agents, retries, and middleware

Two cross-cutting features shape what scenarios assert:

  • Agent retriesagent({ retry: { maxAttempts, baseDelay } }) retries on transient errors. To assert the exhausted-retry path, set expect.status: "failed" and use expect.error (or an assert callback with expectError). See Retry.
  • Middlewaresrc/middleware.ts runs before every /runs/wait and /runs/stream request. Live-server scenarios (run: { url }) exercise middleware; in-process scenarios bypass it. A scenario whose middleware calls reject(...) should set expect.status: "failed" with a matching expect.error. See Middleware.

Mocking tools

Rules

  1. 1

    File location

    run.test.ts must live in the route's directory, not a sibling or nested folder. Dawn matches tests to routes by directory.

  2. 2

    Default-exported array

    The file's default export is the array of scenario records. There is no describe() or test() wrapper.

  3. 3

    Use the helpers from @dawn-ai/sdk/testing for custom assertions

    expectOutput, expectMeta, and expectError cover deep-equal match (with helpful diffs). For partial or fuzzy matching today, use the declarative expect shape (output/meta/error) or write a custom predicate in assert(result).

  4. 4

    Keep scenarios focused

    One scenario = one claim about the route's behavior. If you're adding branches, add more scenarios — don't inflate a single one.

CI

Use dawn verify as the integrity gate (it covers app, routes, typegen, and deps in one call), then run dawn test:

yaml
- run: pnpm exec dawn verify
- run: pnpm exec dawn test

dawn verify runs typegen and check internally, plus the deps check (missing packages, missing env vars) that bare dawn check && dawn typegen does not. dawn test exits non-zero on any failure and outputs a diff per mismatched scenario.

Related