TheBushidoCollective

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.

TheBushidoCollective 190 20 Updated 6mo ago
GitHub

Install

npx skillscat add thebushidocollective/han/bun-testing

Install via the SkillsCat registry.

About this skill

This skill provides expertise in using Bun's built-in test runner for software verification. It assists in organizing test suites, implementing assertions, mocking dependencies, and performing snapshot testing. An agent or developer should use this skill when developing applications within the Bun runtime to ensure code correctness using Jest-compatible APIs.

SKILL.md

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