Claude
Skills
Sign in
Back

bun-test-patterns

Included with Lifetime
$97 forever

Bun test runner - Jest-compatible testing with mocks, snapshots, coverage, and DOM testing patterns When user writes tests with Bun, uses bun:test, creates mocks, runs test coverage, or mentions describe/it/expect patterns

Code Review

What this skill does


# Bun Test Patterns Agent

## What's New in Bun Test (2024-2025)

- **Vitest compatibility**: `vi` alias for easier migration
- **Module mocking**: `mock.module()` for ESM/CJS mocking
- **Type testing**: `expectTypeOf` for TypeScript type assertions
- **Custom matchers**: `expect.extend()` for custom assertions
- **Improved coverage**: Built-in code coverage reporting
- **Watch mode**: Automatic test re-runs on file changes

## Running Tests

### Basic Commands

```bash
# Run all tests
bun test

# Run specific file
bun test math.test.ts

# Run tests matching pattern
bun test --test-name-pattern "add"
bun test -t "add"

# Run with watch mode
bun test --watch

# Run with coverage
bun test --coverage

# Run with timeout
bun test --timeout 10000

# Run specific files by path pattern
bun test src/utils
```

### File Discovery

Bun automatically finds test files matching:

- `*.test.{js|jsx|ts|tsx}`
- `*_test.{js|jsx|ts|tsx}`
- `*.spec.{js|jsx|ts|tsx}`
- `*_spec.{js|jsx|ts|tsx}`

### Configuration (bunfig.toml)

```toml
[test]
# Enable coverage by default
coverage = true

# Set coverage threshold
coverageThreshold = { line = 80, function = 80 }

# Preload scripts
preload = ["./test/setup.ts"]

# Test timeout in ms
timeout = 5000

# Smol mode for reduced memory
smol = true
```

## Writing Tests

### Basic Structure

```typescript
import { describe, test, it, expect, beforeAll, afterEach } from "bun:test";

describe("Calculator", () => {
  describe("add()", () => {
    it("adds two positive numbers", () => {
      expect(add(2, 3)).toBe(5);
    });

    it("handles negative numbers", () => {
      expect(add(-1, 1)).toBe(0);
    });
  });
});
```

### Test Modifiers

```typescript
// Skip a test
test.skip("not ready yet", () => {
  // ...
});

// Run only this test
test.only("focus on this", () => {
  // ...
});

// Mark as todo
test.todo("implement later");

// Conditional skip
test.if(process.env.CI)("only in CI", () => {
  // ...
});

// Skip if condition
test.skipIf(!process.env.DB_URL)("needs database", () => {
  // ...
});
```

### Async Tests

```typescript
// Async/await
test("fetches user", async () => {
  const user = await fetchUser(1);
  expect(user.name).toBe("Alice");
});

// Promise
test("resolves correctly", () => {
  return fetchUser(1).then((user) => {
    expect(user.name).toBe("Alice");
  });
});

// Callback (done)
test("callback style", (done) => {
  setTimeout(() => {
    expect(true).toBe(true);
    done();
  }, 100);
});
```

### Timeout

```typescript
// Per-test timeout
test("slow operation", async () => {
  const result = await slowOperation();
  expect(result).toBeDefined();
}, 10000); // 10 second timeout
```

## Expect Matchers

### Equality

```typescript
expect(value).toBe(expected); // === comparison
expect(value).toEqual(expected); // deep equality
expect(value).toStrictEqual(expected); // strict deep equality
expect(value).not.toBe(other); // negation
```

### Truthiness

```typescript
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeNull();
expect(value).toBeUndefined();
expect(value).toBeDefined();
expect(value).toBeNaN();
```

### Numbers

```typescript
expect(num).toBeGreaterThan(5);
expect(num).toBeGreaterThanOrEqual(5);
expect(num).toBeLessThan(10);
expect(num).toBeLessThanOrEqual(10);
expect(num).toBeCloseTo(0.3, 5); // floating point
expect(num).toBePositive();
expect(num).toBeNegative();
expect(num).toBeInteger();
expect(num).toBeFinite();
```

### Strings

```typescript
expect(str).toMatch(/pattern/);
expect(str).toContain("substring");
expect(str).toStartWith("prefix");
expect(str).toEndWith("suffix");
expect(str).toHaveLength(10);
```

### Arrays and Iterables

```typescript
expect(arr).toContain(item);
expect(arr).toContainEqual({ id: 1 });
expect(arr).toHaveLength(3);
expect(arr).toBeArray();
expect(arr).toBeArrayOfSize(3);
expect(arr).toInclude(item);
expect(arr).toIncludeAllMembers([1, 2]);
expect(arr).toIncludeAnyMembers([1, 5]);
expect(arr).toSatisfyAll((x) => x > 0);
```

### Objects

```typescript
expect(obj).toHaveProperty("key");
expect(obj).toHaveProperty("nested.key", value);
expect(obj).toMatchObject({ subset: true });
expect(obj).toContainKey("key");
expect(obj).toContainKeys(["a", "b"]);
expect(obj).toContainAllKeys(["a", "b"]);
expect(obj).toContainValue(42);
```

### Functions and Errors

```typescript
expect(() => fn()).toThrow();
expect(() => fn()).toThrow("message");
expect(() => fn()).toThrow(Error);
expect(() => fn()).toThrowError(/pattern/);

// Async errors
await expect(asyncFn()).rejects.toThrow();
await expect(asyncFn()).resolves.toBe(value);
```

### Assertions Count

```typescript
test("multiple assertions", () => {
  expect.assertions(3); // Must have exactly 3 assertions

  expect(a).toBe(1);
  expect(b).toBe(2);
  expect(c).toBe(3);
});

test("at least one", async () => {
  expect.hasAssertions(); // Must have at least one assertion

  const data = await fetchData();
  expect(data).toBeDefined();
});
```

## Lifecycle Hooks

### Basic Hooks

```typescript
import { beforeAll, afterAll, beforeEach, afterEach } from "bun:test";

// Run once before all tests in file/describe block
beforeAll(() => {
  console.log("Setting up");
});

// Run once after all tests
afterAll(() => {
  console.log("Tearing down");
});

// Run before each test
beforeEach(() => {
  console.log("Before each test");
});

// Run after each test
afterEach(() => {
  console.log("After each test");
});
```

### Async Hooks

```typescript
beforeAll(async () => {
  await database.connect();
});

afterAll(async () => {
  await database.disconnect();
});
```

### Scoped Hooks

```typescript
describe("outer", () => {
  beforeAll(() => console.log("outer beforeAll"));
  beforeEach(() => console.log("outer beforeEach"));

  describe("inner", () => {
    beforeAll(() => console.log("inner beforeAll"));
    beforeEach(() => console.log("inner beforeEach"));

    test("example", () => {
      // Runs: outer beforeAll, inner beforeAll,
      //       outer beforeEach, inner beforeEach, test
    });
  });
});
```

### Preload Scripts

```typescript
// test/setup.ts - loaded before all tests
import { beforeEach, afterEach, mock } from "bun:test";

// Global setup
beforeEach(() => {
  // Reset mocks before each test
  mock.restore();
});

afterEach(() => {
  // Cleanup after each test
});
```

```toml
# bunfig.toml
[test]
preload = ["./test/setup.ts"]
```

## Mocking

### Mock Functions

```typescript
import { mock, expect, test } from "bun:test";

test("mock function", () => {
  // Create mock
  const mockFn = mock(() => 42);

  // Call it
  const result = mockFn("arg1", "arg2");

  // Assert
  expect(mockFn).toHaveBeenCalled();
  expect(mockFn).toHaveBeenCalledTimes(1);
  expect(mockFn).toHaveBeenCalledWith("arg1", "arg2");
  expect(result).toBe(42);

  // Access call history
  expect(mockFn.mock.calls).toEqual([["arg1", "arg2"]]);
  expect(mockFn.mock.results).toEqual([{ type: "return", value: 42 }]);
});
```

### Mock Implementations

```typescript
const mockFn = mock();

// Set return value
mockFn.mockReturnValue(42);
expect(mockFn()).toBe(42);

// Return once then default
mockFn.mockReturnValueOnce(1).mockReturnValueOnce(2).mockReturnValue(0);
expect(mockFn()).toBe(1);
expect(mockFn()).toBe(2);
expect(mockFn()).toBe(0);

// Custom implementation
mockFn.mockImplementation((x) => x * 2);
expect(mockFn(5)).toBe(10);

// Async mocks
mockFn.mockResolvedValue({ data: "test" });
await expect(mockFn()).resolves.toEqual({ data: "test" });

mockFn.mockRejectedValue(new Error("fail"));
await expect(mockFn()).rejects.toThrow("fail");
```

### Spies

```typescript
import { spyOn, expect, test } from "bun:test";

const calculator = {
  add(a: number, b: number) {
    return a + b;
  },
};

test("spy on method", () => {
  const spy = spyOn(calculator, "add");

  const result = calculator.add(2, 3);

  expect(spy).toHaveBeenCalledWith(2, 3);
  expect(spy).toHaveBeenCalledTimes(1);
  expect(result).toBe(5); // Orig

Related in Code Review