Guides

Testing

Unit, integration, and E2E testing with Bun.

Testing

Bklar is built on Bun, so use the native bun:test runner. It's fast and API-compatible with Jest.

In-Memory Testing

app.request() simulates requests without starting a TCP server — perfect for unit and integration tests.

import { describe, expect, it, beforeEach } from "bun:test";
import { Bklar } from "bklar";

describe("My API", () => {
  let app: ReturnType<typeof Bklar>;

  beforeEach(() => {
    app = Bklar({ logger: false });
    app.get("/hello", (ctx) => ctx.json({ hello: "world" }));
    app.post("/submit", (ctx) => ctx.body, {
      schemas: { body: z.object({ name: z.string() }) },
    });
  });

  it("should return hello world", async () => {
    const res = await app.request("/hello");
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body).toEqual({ hello: "world" });
  });

  it("should validate request bodies", async () => {
    const res = await app.request("/submit", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ name: 123 }),
    });
    expect(res.status).toBe(400);
  });
});

E2E Testing (Real Server)

For full integration tests, start a real server and use fetch():

import { describe, expect, it, afterEach } from "bun:test";
import { Bklar } from "bklar";

describe("E2E", () => {
  let server: ReturnType<typeof Bklar.prototype.listen>;

  afterEach(() => {
    if (server) server.stop(false);
  });

  it("should handle real HTTP requests", async () => {
    const app = Bklar({ logger: false });
    app.get("/", () => ({ status: "ok" }));
    server = app.listen(0); // Port 0 = random available port

    const res = await fetch(`http://localhost:${server.port}/`);
    expect(res.status).toBe(200);
    expect(res.headers.get("X-Request-Id")).toBeTruthy();
  });
});

Testing Middleware Priority

it("should run middleware in priority order", async () => {
  const order: string[] = [];
  const app = Bklar({ logger: false });

  app.use(async (_, next) => { order.push("C"); return next(); }, 10);
  app.use(async (_, next) => { order.push("A"); return next(); }, -10);
  app.get("/", () => { order.push("H"); return "ok"; });

  await app.request("/");
  expect(order).toEqual(["A", "C", "H"]);
});

Testing SSE

it("should stream SSE events", async () => {
  const app = Bklar({ logger: false });
  app.get("/events", (ctx) => {
    const sse = ctx.sse();
    sse.send("ping", JSON.stringify({ t: Date.now() }));
    sse.close();
    return undefined;
  });

  const res = await app.request("/events");
  expect(res.headers.get("Content-Type")).toBe("text/event-stream");
  const body = await res.text();
  expect(body).toContain("event: ping");
});

Mocking Services

import { spyOn } from "bun:test";

it("should handle db errors", async () => {
  const spy = spyOn(db, "findUser").mockRejectedValue(new Error("DB Down"));
  const res = await app.request("/users/1");
  expect(res.status).toBe(500);
  spy.mockRestore();
});
# Run all tests
bun test

# Run specific file
bun test tests/app.test.ts
# E2E tests use real ports — ensure they're available
bun test tests/e2e.test.ts

On this page