Testing
Testing
Section titled “Testing”What is tested, how endpoint tests run against real infrastructure, and the helpers the framework provides.
Requirements
Section titled “Requirements”bun run db:up # PostgreSQL :54329 + Redis :63799 (compose)cp .env.test.example .env.test # TEST_DATABASE_URL=postgres://… (a server-level admin URL)TEST_DATABASE_URL must point at a dedicated test server/admin URL —
never the application’s DATABASE_URL; the testing package refuses to
create test databases on the application target or in APP_ENV=production.
Isolated databases
Section titled “Isolated databases”Every database test gets its own database (fw_test_<random>), created from
TEST_DATABASE_URL, migrated from the app’s database/migrations, and
dropped in success and failure paths. Nothing leaks between runs.
Application-level tests use the generated tests/support.ts:
const ctx = await createTestContext(); // boots the full app on a fresh migrated DBconst response = await ctx.client.request('/api/v1/products', jsonInit('POST', { name: 'x', price: '1' }));await ctx.close(); // stops server, shuts down, drops the DBTEST_MODE=network swaps in-process http.request for a real loopback
listener (same assertions, real transport). The generated endpoint suites
run identically in both modes — make:crud --verify --test-mode network
proves routes over the wire.
What the generated suites cover
Section titled “What the generated suites cover”Each make:crud module ships tests/<resource>.crud.api.test.ts, which
calls the registered Hono application (never a mocked controller):
- happy path: create → list → read → update → re-read → delete → 404
- public allowlist exposure (secret fields never serialized)
- deterministic, bounded listing; pagination bounds (422)
- malformed JSON (400), malformed ids (422), unknown ids (404)
- invalid payloads: missing / mistyped / refinement-violating / forbidden fields
- empty updates, immutable fields (
createdAt)
make:crud --verify runs this suite plus a typecheck after every generation
and reports each route, pass/fail counts and database cleanup in the result
(JSON with --json).
Framework package tests
Section titled “Framework package tests”Package tests (packages/*/tests) exercise real adapters where it matters:
CRUD mixins, auditing, transactions and pagination run on real PostgreSQL;
queues run on real Redis; events tests cover both buses. Pattern:
const database = await createTestDatabase({ adminUrl: requireTestDatabaseUrl() });const connection = createConnection({ url: database.url, maxConnections: 4 });await connection.sql`create table …`;// … assertions …await connection.close();await database.drop();Fakes — and their limits
Section titled “Fakes — and their limits”MemoryEventBus stands in for Redis in unit tests; the testing package
provides isolated databases instead of a fake query layer. Fakes prove
wiring, never infrastructure behavior — retry, duplicate delivery and
failure semantics are only proven by the real-adapter suites.
Generator and release gates
Section titled “Generator and release gates”packages/cli/tests/generated-app.test.ts—make:crudend to end on new and existing tables, route collisions,--dry-runwriting nothing, and the generated application’s full suite.packages/cli/tests/create-app.test.ts— thecreaterelease gate: the generated application typechecks, passes its suite, migrates the audit table, acceptsmake:crud --verify, andcreate --with-demoproduces a working products module whose endpoint tests pass as-is.apps/playground/tests/contract.test.ts— OpenAPI drift check and a typed Hono RPC client against the composed application.
Run everything from the workspace root:
bun test # or: bun run testConventions
Section titled “Conventions”- No skipped-if-missing tests: missing
TEST_DATABASE_URLfails with setup instructions instead of silently passing. - Tests never auto-migrate the application database; they only migrate their own isolated database.
- Ephemeral network servers bind
127.0.0.1:0and always close.