home / skills / secondsky / claude-skills / bun-jest-migration

bun-jest-migration skill

/plugins/bun/skills/bun-jest-migration

This skill helps migrate Jest tests to Bun's test runner, simplifying imports, mocks, timers, and configuration for seamless compatibility.

npx playbooks add skill secondsky/claude-skills --skill bun-jest-migration

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
5.5 KB
---
name: Bun Jest Migration
description: Use when migrating from Jest to Bun's test runner, import compatibility, mocks, and config.
version: 1.0.0
---

# Bun Jest Migration

Bun's test runner is Jest-compatible. Most Jest tests run without changes.

## Quick Migration

```bash
# 1. Remove Jest dependencies
bun remove jest ts-jest @types/jest babel-jest

# 2. Update test script
# package.json: "test": "bun test"

# 3. Run tests
bun test
```

## Import Changes

```typescript
// Before (Jest)
import { describe, it, expect, jest } from '@jest/globals';

// After (Bun) - No import needed, or explicit:
import { describe, test, expect, mock, spyOn } from "bun:test";
```

## API Compatibility

### Fully Compatible

| Jest | Bun | Notes |
|------|-----|-------|
| `describe()` | `describe()` | Identical |
| `it()` / `test()` | `test()` | Use `test()` |
| `expect()` | `expect()` | Same matchers |
| `beforeAll/Each` | `beforeAll/Each` | Identical |
| `afterAll/Each` | `afterAll/Each` | Identical |
| `jest.fn()` | `mock()` | Use `mock()` |
| `jest.spyOn()` | `spyOn()` | Identical |

### Requires Changes

| Jest | Bun Equivalent |
|------|----------------|
| `jest.mock('module')` | `mock.module('module', () => {...})` |
| `jest.useFakeTimers()` | `import { setSystemTime } from "bun:test"` |
| `jest.setTimeout()` | Third argument to `test()` |
| `jest.clearAllMocks()` | Call `.mockClear()` on each mock |

## Mock Migration

### Mock Functions

```typescript
// Jest
const fn = jest.fn().mockReturnValue('value');

// Bun
const fn = mock(() => 'value');
// Or for compatibility:
import { jest } from "bun:test";
const fn = jest.fn(() => 'value');
```

### Module Mocking

```typescript
// Jest (top-level hoisting)
jest.mock('./utils', () => ({
  helper: jest.fn(() => 'mocked')
}));

// Bun (inline, no hoisting)
import { mock } from "bun:test";
mock.module('./utils', () => ({
  helper: mock(() => 'mocked')
}));
```

### Spy Migration

```typescript
// Jest
jest.spyOn(console, 'log').mockImplementation(() => {});

// Bun (identical)
spyOn(console, 'log').mockImplementation(() => {});
```

## Timer Migration

```typescript
// Jest
jest.useFakeTimers();
jest.setSystemTime(new Date('2024-01-01'));
jest.advanceTimersByTime(1000);

// Bun - supports Jest-compatible timer APIs
import { setSystemTime } from "bun:test";
import { jest } from "bun:test";

jest.useFakeTimers();
jest.setSystemTime(new Date('2024-01-01'));
jest.advanceTimersByTime(1000);  // Now supported
```

## Snapshot Testing

```typescript
// Jest
expect(component).toMatchSnapshot();
expect(data).toMatchInlineSnapshot(`"expected"`);

// Bun (identical)
expect(component).toMatchSnapshot();
expect(data).toMatchInlineSnapshot(`"expected"`);
```

Update snapshots:
```bash
bun test --update-snapshots
```

## Configuration Migration

### jest.config.js → bunfig.toml

```javascript
// jest.config.js (before)
module.exports = {
  testMatch: ['**/*.test.ts'],
  testTimeout: 10000,
  setupFilesAfterEnv: ['./jest.setup.ts'],
  collectCoverage: true,
  coverageThreshold: { global: { lines: 80 } }
};
```

```toml
# bunfig.toml (after)
[test]
root = "./"
preload = ["./jest.setup.ts"]
timeout = 10000
coverage = true
coverageThreshold = 0.8
```

## Common Migration Issues

### Issue: `jest.mock` Not Working

```typescript
// Jest mock hoisting doesn't exist in Bun
// Move mock.module before imports or use dynamic imports

// Solution 1: Use mock.module at top
mock.module('./api', () => ({ fetch: mock() }));
import { fetch } from './api';

// Solution 2: Dynamic import
const mockFetch = mock();
mock.module('./api', () => ({ fetch: mockFetch }));
const { fetch } = await import('./api');
```

### Issue: Timer Functions Missing

```typescript
// Bun timer support is limited
// Use setSystemTime for date mocking
import { setSystemTime } from "bun:test";

beforeEach(() => {
  setSystemTime(new Date('2024-01-01'));
});

afterEach(() => {
  setSystemTime(); // Reset to real time
});
```

### Issue: Custom Matchers

```typescript
// Jest
expect.extend({ toBeWithinRange(received, floor, ceiling) {...} });

// Bun (same API)
import { expect } from "bun:test";
expect.extend({
  toBeWithinRange(received, floor, ceiling) {
    const pass = received >= floor && received <= ceiling;
    return {
      pass,
      message: () => `expected ${received} to be within ${floor}-${ceiling}`
    };
  }
});
```

## Step-by-Step Migration

1. **Remove Jest packages**
   ```bash
   bun remove jest ts-jest @types/jest babel-jest jest-environment-jsdom
   ```

2. **Update package.json**
   ```json
   {
     "scripts": {
       "test": "bun test",
       "test:watch": "bun test --watch",
       "test:coverage": "bun test --coverage"
     }
   }
   ```

3. **Convert jest.config.js to bunfig.toml**

4. **Update imports in test files**
   - Find/replace `@jest/globals` → `bun:test`
   - Find/replace `jest.fn()` → `mock()`
   - Find/replace `jest.mock()` → `mock.module()`

5. **Run and fix**
   ```bash
   bun test 2>&1 | head -50  # Check first errors
   ```

## Common Errors

| Error | Cause | Fix |
|-------|-------|-----|
| `Cannot find module '@jest/globals'` | Old import | Use `bun:test` |
| `jest is not defined` | Global jest | Import from `bun:test` |
| `mock.module is not a function` | Wrong import | `import { mock } from "bun:test"` |
| `Snapshot mismatch` | Different serialization | Update with `--update-snapshots` |

## When to Load References

Load `references/compatibility-matrix.md` when:
- Full Jest API compatibility details
- Unsupported features list
- Workarounds for missing features

Overview

This skill helps teams migrate test suites from Jest to Bun's test runner with minimal friction. It covers dependency removal, import and API changes, mock and timer migration, snapshot handling, and bunfig.toml configuration. Practical guidance and common fixes help get tests passing quickly in TypeScript projects.

How this skill works

The skill inspects project test files and recommends replacements for Jest-specific imports and APIs, shows how to convert mocks and timers to Bun equivalents, and translates jest.config.js settings into bunfig.toml. It also documents commands to remove Jest packages, update npm scripts to bun test, and steps to run tests and update snapshots. Troubleshooting notes cover hoisting differences, missing timer features, and common error messages.

When to use it

  • Migrating an existing TypeScript codebase from Jest to Bun's test runner.
  • Converting jest.config.js to bunfig.toml and aligning test scripts.
  • Replacing Jest mock and spy patterns with Bun-compatible APIs.
  • Fixing failing tests caused by mock hoisting or timer differences.
  • Updating snapshots and running coverage with Bun.

Best practices

  • Remove Jest packages (jest, ts-jest, @types/jest, babel-jest, jest-environment-jsdom) before running Bun tests.
  • Replace @jest/globals imports with bun:test or rely on Bun globals to avoid errors.
  • Use mock.module for module-level mocks and place them before imports or use dynamic import workarounds.
  • Prefer test() over it() where Bun suggests, and pass timeout as the third argument to test() for per-test timeouts.
  • Run bun test --update-snapshots after major serialization differences and check the first errors with bun test 2>&1 | head -50.

Example use cases

  • A React + TypeScript project switching CI from Node/Jest to Bun to gain performance improvements.
  • Migrating a monorepo test suite that uses jest.mock hoisting into Bun with inline mock.module calls.
  • Converting timer-based tests to use setSystemTime and Bun's jest-compatible timer APIs.
  • Updating project test scripts to run bun test and enabling coverage in bunfig.toml.
  • Resolving 'Cannot find module "@jest/globals"' errors by replacing imports and updating mocks.

FAQ

Do I need to change all jest.fn() calls?

You can replace jest.fn() with mock() or import jest from "bun:test" and keep jest.fn() for compatibility; mock() is the preferred Bun API.

How do I convert jest.config.js to bunfig.toml?

Map testMatch → [test].root or file patterns, setupFilesAfterEnv → preload, testTimeout → timeout, and coverage settings to coverage and coverageThreshold in bunfig.toml.