Testing
bext gives PRISM apps the two testing ergonomics you know from Laravel and
Testing Library, both runnable in plain bun test:
@bext-stack/framework/testkit— model factories for seeded test data and a route test client that invokes a route'sloader/actionwith a synthesized request. Pure, no DOM, no server.@bext-stack/framework/testing— component testing: render a"use signals"component, query it, interact, and assert (backed by happy-dom).
Factories#
import { defineFactory, cycle } from "@bext-stack/framework/testkit";
const userFactory = defineFactory((n) => ({
id: `u${n}`,
email: `user${n}@example.com`, // `n` gives unique fields
role: cycle("admin", "member")(n), // cycle by index
active: true,
}));
const users = userFactory.makeMany(3); // 3 seeded users
const admin = userFactory.make({ role: "admin" }); // one, overridden
const admins = userFactory.state(() => ({ role: "admin" })).makeMany(2); // a variant (states)
make/makeMany advance a sequence so unique fields stay unique;
makeMany(n, i => ({...})) takes per-index overrides; state(fn) derives a
variant factory (Laravel factory states); reset() zeroes the sequence.
Route test client#
testRoute(module) wraps a route's loader/action exports and calls them with
a synthesized Request:
import { testRoute } from "@bext-stack/framework/testkit";
import * as signup from "../app/signup/page";
test("rejects an invalid signup", async () => {
const route = testRoute(signup);
const res = await route.post({ email: "nope", age: "12" }); // form-encoded → action
expect(res.ok).toBe(false);
});
test("loader reads query params", async () => {
expect(await testRoute(signup).get({ ref: "promo" })).toMatchObject({ ref: "promo" });
});
| Member | Calls |
|---|---|
.get(query?) |
the loader with a GET request |
.post(fields?) |
the action with a form-encoded POST |
.postJson(body) |
the action with a JSON POST |
.request(req) |
either handler with an explicit Request (custom headers/cookies) |
Standalone request builders — formData(obj), formRequest(url, fields),
jsonRequest(url, body) — are exported too, and factories feed action inputs
directly: route.post(userFactory.make()).
Component testing#
import { render } from "@bext-stack/framework/testing";
const { text, getByTestId, click } = render(Counter, { start: 5 });
expect(text()).toContain("Count: 5");
click(getByTestId("inc"));
expect(text()).toContain("Count: 6");
render SSRs + hydrates the component in a happy-dom container and returns
queries (getByText, getByTestId, getAllByTag) and interactions (click,
type, fire). Import it only from test files — it pulls in happy-dom.
Try It#
The live testkit demo generates factory
data and runs testRoute assertions in the isolate — the same helpers you'd use
in bun test. Source in sites/demo/src/app/examples/testkit/page.tsx.
See Also#
- Validation — the action logic you'll assert on.
- Data & Migrations — factories pair with the ORM for seeding.
- Application Toolkit — the rest of the TypeScript app layer.