Testing
Unit, integration, and E2E — all through Bun's test runner with Mandu's ATE auto-generating contract tests for you.
On this page
Testing
TL;DR —
bun testfor unit/integration,bun run test:e2efor browser,bun run mandu ate generateto auto-write contract tests.
Why
Tests are the second-most-skipped thing in agent-generated code (after error handling). Mandu makes the test boilerplate disappear — ATE reads your contracts and emits the tests. Your job becomes "review the generated test, add the cases ATE missed".
Unit tests
// src/server/domain/users.test.ts
import { describe, expect, test } from "bun:test";
import { createUser } from "./users";
describe("createUser", () => {
test("normalises email to lowercase", () => {
const u = createUser({ email: "ME@example.com" });
expect(u.email).toBe("me@example.com");
});
});
bun test
bun test --watch # re-run on save
bun test src/server/domain # just one folder
Auto-generated contract tests
bun run mandu ate generate
# writes tests/_generated/<contract>.test.ts for every Mandu.contract({...})
ATE inspects the request/response zod schemas, generates positive cases (every required field, every optional combination) and negative cases (missing fields, wrong types, boundary values). Edit the generator config in mandu.config.ts ate section — never the generated files directly.
E2E
// tests/e2e/login.spec.ts
import { test, expect } from "@playwright/test";
test("user can log in", async ({ page }) => {
await page.goto("http://localhost:3333/login");
await page.fill('input[name="email"]', "demo@example.com");
await page.fill('input[name="password"]', "demo1234");
await page.click('button[type="submit"]');
await expect(page).toHaveURL(/\/dashboard/);
});
bun run test:e2e # assumes dev server on :3333
bun run test:e2e:server # boots dev + runs E2E + tears down
🤖 Agent Prompt
Add tests to my Mandu project:
1. Pure functions → `<file>.test.ts` next to the source.
`import { describe, expect, test } from 'bun:test'`.
2. API contracts → `bun run mandu ate generate`, then review
`tests/_generated/`. Add missing edge cases in a sibling
`<contract>.cases.test.ts` (don't edit the generated file).
3. E2E flows → `tests/e2e/<flow>.spec.ts` using `@playwright/test`.
Assume dev server is on http://localhost:3333.
Required invariants:
- Use `bun:test`, NEVER `vitest` or `jest`.
- Unit tests sit next to source; integration in `tests/`; E2E in `tests/e2e/`.
- `tests/_generated/` is read-only — ATE regenerates.
- DB-backed tests use `tests/setup-db.ts`; no shared state.
After writing, run `bun test` (and `bun run test:e2e:server` if E2E
was added) and report any failures.
Pitfalls
- Don't import from
vitest. Mandu's runner isbun:test. Vitest matchers occasionally compile but blow up on coverage. tests/_generated/is hands-off. ATE rewrites it. Put your additional cases in a sibling<name>.cases.test.ts.- Shared DB state breaks integration tests. Always provision a fresh in-memory SQLite via
setup-db.ts. - E2E without dev server. A bare
bun run test:e2eerrors withECONNREFUSED 3333. Use:servervariant or runbun run devin another terminal.
Related
- testing/ — full per-flavour pages (unit, integration, E2E, coverage, watch, snapshot)
- build-with-agents/ate — how ATE generates tests
For Agents
Use `bun test` for unit + integration. Mandu's ATE (Automated Test Engineer) auto-generates contract tests via `bun run mandu ate generate`. E2E runs through Playwright via `bun run test:e2e`. Tests live next to the file (`*.test.ts`) for unit, in `tests/` for integration, and in `tests/e2e/` for browser tests.
- Unit tests live alongside the source as `*.test.ts`; integration tests live in `tests/`; E2E in `tests/e2e/`
- Test runner is Bun (`bun test`); never `vitest` or `jest`
- Generated contract tests live under `tests/_generated/` and must NOT be hand-edited (ATE regenerates them)
- For DB-backed tests, use `tests/setup-db.ts` to provision an in-memory SQLite instance — never share DB state across tests
- E2E tests require `bun run dev` running on port 3333 (or `bun run test:e2e:server` to manage automatically)