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