Claude-skill-registry jutsu-bun:bun-testing
Use when writing tests with Bun's built-in test runner. Covers test organization, assertions, mocking, and snapshot testing using Bun's fast test infrastructure.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/bun-testing" ~/.claude/skills/majiayu000-claude-skill-registry-jutsu-bun-bun-testing && rm -rf "$T"
manifest:
skills/data/bun-testing/SKILL.mdsource content
Bun Testing
Use this skill when writing tests with Bun's built-in test runner, which provides Jest-compatible APIs with significantly faster execution.
Key Concepts
Test Runner Basics
Bun includes a built-in test runner that works out of the box:
import { test, expect, describe, beforeAll, afterAll } from "bun:test"; describe("Math operations", () => { test("addition", () => { expect(1 + 1).toBe(2); }); test("subtraction", () => { expect(5 - 3).toBe(2); }); });
Running Tests
# Run all tests bun test # Run specific test file bun test ./src/utils.test.ts # Run with coverage bun test --coverage # Watch mode bun test --watch
Matchers and Assertions
Bun supports Jest-compatible matchers:
import { test, expect } from "bun:test"; test("matchers", () => { // Equality expect(42).toBe(42); expect({ a: 1 }).toEqual({ a: 1 }); // Truthiness expect(true).toBeTruthy(); expect(false).toBeFalsy(); expect(null).toBeNull(); expect(undefined).toBeUndefined(); // Numbers expect(10).toBeGreaterThan(5); expect(3).toBeLessThan(5); expect(3.14).toBeCloseTo(3.1, 1); // Strings expect("hello world").toContain("hello"); expect("test@example.com").toMatch(/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/); // Arrays expect([1, 2, 3]).toContain(2); expect([1, 2, 3]).toHaveLength(3); // Objects expect({ a: 1, b: 2 }).toHaveProperty("a"); expect({ a: 1, b: 2 }).toMatchObject({ a: 1 }); // Errors expect(() => { throw new Error("Test error"); }).toThrow("Test error"); });
Best Practices
Organize Tests with describe/test
Structure tests in a clear hierarchy:
import { describe, test, expect } from "bun:test"; describe("UserService", () => { describe("createUser", () => { test("creates user with valid data", () => { // Test implementation }); test("throws error with invalid email", () => { // Test implementation }); }); describe("findUser", () => { test("finds existing user by id", () => { // Test implementation }); test("returns null for non-existent user", () => { // Test implementation }); }); });
Use Setup and Teardown Hooks
Clean up state between tests:
import { describe, test, beforeAll, afterAll, beforeEach, afterEach } from "bun:test"; describe("Database tests", () => { beforeAll(() => { // Run once before all tests console.log("Setting up test database"); }); afterAll(() => { // Run once after all tests console.log("Tearing down test database"); }); beforeEach(() => { // Run before each test console.log("Resetting test data"); }); afterEach(() => { // Run after each test console.log("Cleaning up test data"); }); test("example test", () => { expect(true).toBe(true); }); });
Mocking with Bun
Use Bun's built-in mocking:
import { test, expect, mock } from "bun:test"; test("mocking functions", () => { const mockFn = mock((x: number) => x * 2); mockFn(2); mockFn(3); expect(mockFn).toHaveBeenCalledTimes(2); expect(mockFn).toHaveBeenCalledWith(2); expect(mockFn).toHaveBeenCalledWith(3); expect(mockFn.mock.results[0].value).toBe(4); }); test("mocking modules", async () => { // Mock a module mock.module("./api", () => ({ fetchData: mock(() => Promise.resolve({ data: "mocked" })), })); const { fetchData } = await import("./api"); const result = await fetchData(); expect(result).toEqual({ data: "mocked" }); });
Async Testing
Handle asynchronous code properly:
import { test, expect } from "bun:test"; test("async function", async () => { const data = await fetchData(); expect(data).toBeDefined(); }); test("promises", () => { return fetchData().then((data) => { expect(data).toBeDefined(); }); }); test("async/await with error", async () => { await expect(async () => { await fetchInvalidData(); }).toThrow("Invalid data"); });
Common Patterns
Testing HTTP Endpoints
import { describe, test, expect } from "bun:test"; describe("API endpoints", () => { test("GET /api/users returns users list", async () => { const response = await fetch("http://localhost:3000/api/users"); const users = await response.json(); expect(response.status).toBe(200); expect(Array.isArray(users)).toBe(true); }); test("POST /api/users creates new user", async () => { const newUser = { name: "Alice", email: "alice@example.com" }; const response = await fetch("http://localhost:3000/api/users", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(newUser), }); expect(response.status).toBe(201); const user = await response.json(); expect(user).toMatchObject(newUser); expect(user.id).toBeDefined(); }); });
Testing File Operations
import { test, expect, beforeEach, afterEach } from "bun:test"; import { unlink } from "fs/promises"; describe("File operations", () => { const testFile = "./test-output.txt"; afterEach(async () => { try { await unlink(testFile); } catch {} }); test("writes file successfully", async () => { await Bun.write(testFile, "test content"); const file = Bun.file(testFile); expect(await file.exists()).toBe(true); const content = await file.text(); expect(content).toBe("test content"); }); });
Snapshot Testing
import { test, expect } from "bun:test"; test("snapshot test", () => { const data = { id: 1, name: "Alice", email: "alice@example.com", }; expect(data).toMatchSnapshot(); });
Parameterized Tests
import { test, expect } from "bun:test"; const testCases = [ { input: 1, expected: 2 }, { input: 2, expected: 4 }, { input: 3, expected: 6 }, ]; testCases.forEach(({ input, expected }) => { test(`double(${input}) should equal ${expected}`, () => { expect(double(input)).toBe(expected); }); });
Testing with Timers
import { test, expect } from "bun:test"; test("delayed execution", async () => { let executed = false; setTimeout(() => { executed = true; }, 100); await new Promise((resolve) => setTimeout(resolve, 150)); expect(executed).toBe(true); });
Anti-Patterns
Don't Use External Test Runners
// Bad - Installing Jest or other test runners // package.json { "devDependencies": { "jest": "^29.0.0" } } // Good - Use Bun's built-in test runner bun test
Don't Forget to Clean Up
// Bad - Test pollution test("test 1", () => { globalState.value = 10; expect(globalState.value).toBe(10); }); test("test 2", () => { // May fail due to test 1's state expect(globalState.value).toBe(0); }); // Good - Clean state import { beforeEach } from "bun:test"; beforeEach(() => { globalState.value = 0; });
Don't Test Implementation Details
// Bad - Testing private methods test("private method", () => { const instance = new MyClass(); expect(instance._privateMethod()).toBe(true); }); // Good - Test public API test("public behavior", () => { const instance = new MyClass(); const result = instance.publicMethod(); expect(result).toBe(expectedValue); });
Don't Write Flaky Tests
// Bad - Timing-dependent test test("flaky test", () => { setTimeout(() => { expect(value).toBe(10); }, 50); // May fail on slow systems }); // Good - Deterministic test test("reliable test", async () => { await performAsyncOperation(); expect(value).toBe(10); });
Related Skills
- bun-runtime: Core Bun runtime APIs and functionality
- bun-package-manager: Managing test dependencies
- bun-bundler: Building test files for different environments