bext.dev
DocsApplication Toolkit
Application Toolkit2 min read
On this page

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/testkitmodel factories for seeded test data and a route test client that invokes a route's loader/action with a synthesized request. Pure, no DOM, no server.
  • @bext-stack/framework/testingcomponent testing: render a "use signals" component, query it, interact, and assert (backed by happy-dom).

Factories#

ts
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:

ts
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#

ts
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#

Edit this page ↗Need a hand? ↗
FIND YOUR NEXT STEP

Start with a topic, a command, or a question.