This guide explains how to test React components and hooks in frontend plugins using Bun’s test runner with Happy DOM.
Add @checkstack/test-utils-frontend to your package’s devDependencies:
{
"devDependencies": {
"@checkstack/test-utils-frontend": "workspace:*"
}
}
Create a bunfig.toml file in your package root:
[test]
preload = ["@checkstack/test-utils-frontend/setup"]
This single preload file:
document, window, etc.)expect with Testing Library matchers (toBeInTheDocument, etc.)import { describe, it, expect } from "bun:test";
import { render, screen } from "@testing-library/react";
import { MyComponent } from "./MyComponent";
describe("MyComponent", () => {
it("renders correctly", () => {
render(<MyComponent title="Hello" />);
expect(screen.getByText("Hello")).toBeInTheDocument();
});
});
Use renderHook from @testing-library/react:
import { describe, it, expect } from "bun:test";
import { renderHook, act } from "@testing-library/react";
import { useCounter } from "./useCounter";
describe("useCounter", () => {
it("increments count", () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
});
cd your-package
bun test
Or from the monorepo root:
bun test --filter your-package
The @checkstack/test-utils-frontend package includes:
| Package | Purpose |
|---|---|
@happy-dom/global-registrator |
Provides DOM globals for Bun |
@testing-library/react |
React component/hook testing utilities |
@testing-library/dom |
DOM querying utilities |
@testing-library/jest-dom |
Custom matchers like toBeInTheDocument |
For convenience, common utilities are re-exported from the main entry:
import { render, screen, renderHook, act, waitFor } from "@checkstack/test-utils-frontend";
Testing hooks with async useEffect requires careful handling. Happy DOM’s event loop doesn’t automatically flush async operations like a real browser.
Recommended approach: Test synchronous behavior (method calls, state changes) rather than waiting for async fetch completion:
it("should call fetch with correct params", () => {
const mockFetch = mock(async () => ({ data: [] }));
renderHook(() => useMyHook({ fetchFn: mockFetch }));
// Test that the fetch was called with correct params
expect(mockFetch).toHaveBeenCalledWith({ limit: 10 });
});
For end-to-end testing of frontend plugins, use Playwright through the same @checkstack/test-utils-frontend package.
import { createPlaywrightConfig } from "@checkstack/test-utils-frontend/playwright";
export default createPlaywrightConfig({
baseURL: "http://localhost:5173",
});
Create an e2e/ directory and add test files with .e2e.ts extension:
Important: Use
.e2e.tsextension (not.spec.tsor.test.ts) to ensure E2E tests are not picked up bybun testwhen running unit tests from the monorepo root. The.e2e.tspattern is specifically excluded from Bun’s default test discovery.
// e2e/login.e2e.ts
import { test, expect } from "@checkstack/test-utils-frontend/playwright";
test.describe("Login Page", () => {
test("should display the login page", async ({ page }) => {
await page.goto("/login");
await expect(page.locator("h1")).toBeVisible();
});
});
{
"scripts": {
"test:e2e": "bunx playwright test"
}
}
Prerequisites: Both frontend and backend dev servers must be running.
# Terminal 1: Start backend
cd core/backend && bun run dev
# Terminal 2: Start frontend
cd core/frontend && bun run dev
# Terminal 3: Run E2E tests
cd plugins/your-plugin && bun run test:e2e
On first run, install the required browsers:
bunx playwright install chromium
createPlaywrightConfig({
baseURL: "http://localhost:5173", // Frontend URL
testDir: "./e2e", // Test directory
webServer: { // Optional: auto-start dev server
command: "bun run dev",
url: "http://localhost:5173",
reuseExistingServer: true,
},
});
data-testid, roles, or text content over CSS classes