⚠️ AlabJS is under active development and not yet production-ready. APIs may change before v1.0. Feel free to explore, contribute, or star the repo.
Skip to content

alabjs/test provides utilities for testing AlabJS pages and server functions without starting an HTTP server. Tests run entirely in-process using Vitest.

Setup

AlabJS's Vite plugin handles Vitest configuration automatically. Add a test script to your package.json:

json
{
  "scripts": {
    "test": "vitest"
  }
}

No vitest.config.ts needed — AlabJS's vite.config.ts (generated by the Vite plugin) already includes the Vitest configuration.

Rendering a page

renderPage resolves the correct page component from the file system and renders it to an HTML string:

ts
import { renderPage } from "alabjs/test";

test("renders the home page", async () => {
  const { html, status } = await renderPage("/");
  expect(status).toBe(200);
  expect(html).toContain("<h1>Welcome</h1>");
});

For dynamic routes, pass params:

ts
const { html } = await renderPage("/posts/hello-world", {
  params: { slug: "hello-world" },
});

Mocking server functions

mockServerFn replaces a server function's return value for the duration of the test:

ts
import { renderPage, mockServerFn, clearMocks } from "alabjs/test";
import { getUser } from "./app/users/[id]/page.server";

afterEach(() => clearMocks());

test("renders user name", async () => {
  mockServerFn(getUser, { id: "1", name: "Alice", email: "alice@example.com" });

  const { html } = await renderPage("/users/1", { params: { id: "1" } });
  expect(html).toContain("Alice");
});

For dynamic responses, use mockServerFnWith:

ts
import { mockServerFnWith } from "alabjs/test";

mockServerFnWith(getUser, async ({ params }) => {
  if (params.id === "1") return { name: "Alice" };
  throw new Error("Not found");
});

Rendering a component directly

renderComponent renders any React component to HTML without routing:

ts
import { renderComponent } from "alabjs/test";
import UserCard from "./app/components/UserCard";

test("renders user card", async () => {
  const { html, status } = await renderComponent(UserCard, {
    user: { name: "Bob", role: "admin" },
  });
  expect(status).toBe(200);
  expect(html).toContain("Bob");
  expect(html).toContain("admin");
});

Testing server functions directly

Server functions are plain async functions — call them directly in tests:

ts
import { createPost } from "./app/posts/page.server";

// Mock the database
vi.mock("../db", () => ({
  posts: {
    create: vi.fn().mockResolvedValue({ id: "1", title: "Test" }),
  },
}));

test("createPost returns new post", async () => {
  const post = await createPost({ title: "Test" });
  expect(post.id).toBe("1");
});

Testing mutations

For mutations that call useMutation hooks, test the server function directly — the hook is a thin wrapper around fetch:

ts
import { createComment } from "./app/posts/[id]/page.server";

test("createComment validates input", async () => {
  await expect(createComment({ body: "" })).rejects.toThrow("body is required");
});

Error scenarios

renderPage catches errors and returns them:

ts
test("handles missing user", async () => {
  mockServerFn(getUser, null); // Simulate not-found

  const { html, status, error } = await renderPage("/users/999", {
    params: { id: "999" },
  });
  expect(status).toBe(500); // Error thrown during render
  expect(error).not.toBeNull();
});

API Reference

renderPage(path, options?): Promise<RenderPageResult>

Renders the page at path to HTML.

ts
interface RenderPageOptions {
  params?: Record<string, string>;
  searchParams?: Record<string, string>;
  headers?: Record<string, string>;
}

interface RenderPageResult {
  html: string;
  status: number;
  error: Error | null;
}

Returns status: 404 if no page component is found. Returns status: 500 if the component throws.

renderComponent<P>(Component, props): Promise<RenderPageResult>

Renders Component with props to HTML. Lower-level than renderPage — use when you want to test a specific component regardless of routing.

mockServerFn(fn, returnValue): void

Mocks the server function fn to return returnValue when called. Type-safe: TypeScript infers the return type from the server function signature.

mockServerFnWith(fn, handler): void

Mocks the server function fn with a custom async handler.

ts
mockServerFnWith(getUser, async ({ params }, input) => {
  // params: route params
  // input: the argument passed to the server function
  return { id: params.id, name: "Test User" };
});

clearMocks(): void

Removes all server function mocks. Call in afterEach for test isolation.

Released under the MIT License.