Skip to main content
skill CX Codex

bun-jest-migration — Claude Code skill: move tests from Jest to Bun

Walks a Jest test suite over to Bun's built-in runner: imports, mocks, timers and config.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add secondsky/claude-skills --skill bun-jest-migration -a claude-code
Open on GitHub

What bun-jest-migration does

Bun's runner is Jest-compatible, so most of your suite runs untouched. This skill covers the parts that don't. describe, expect, beforeAll and spyOn carry over as-is. jest.fn() becomes mock(), jest.mock('module') becomes mock.module('module', () => {...}), and jest.clearAllMocks() has to be replaced with a .mockClear() call on each mock.

Timers are the other rough edge. jest.useFakeTimers() and jest.advanceTimersByTime() are supported, but for date mocking the skill points at setSystemTime from bun:test. Mock hoisting is gone: Jest lifts jest.mock to the top of the file, Bun doesn't, so mock.module goes above the imports or the module gets pulled in with a dynamic import. Any test that leans on hoisting will break — check those first.

Config moves from jest.config.js to bunfig.toml. testMatch gives way to root, setupFilesAfterEnv to preload, and coverageThreshold is written as a fraction such as 0.8. Snapshots transfer unchanged and refresh with bun test --update-snapshots. For the full compatibility picture the skill says to load references/compatibility-matrix.md.

What it helps with

  • strip jest, ts-jest and babel-jest from the project
  • swap @jest/globals imports for bun:test
  • rewrite jest.mock calls as mock.module without hoisting
  • port settings from jest.config.js into bunfig.toml
  • fix snapshots after the serialization change

Example prompts for the agent

  • migrate this project's tests from Jest to Bun
  • why doesn't jest.mock work under Bun and how do I fix it
  • convert jest.config.js to bunfig.toml

How to install bun-jest-migration

1Skills CLI (Vercel)

npx skills add secondsky/claude-skills --skill bun-jest-migration -a claude-code

2Manually: put the files in place

~/.claude/skills/bun-jest-migration/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/bun-jest-migration/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about bun-jest-migration

How do I install bun-jest-migration in Claude?

Run npx skills add secondsky/claude-skills --skill bun-jest-migration -a claude-code. The skill lands at ~/.claude/skills/bun-jest-migration/SKILL.md.

What does bun-jest-migration do?

It guides a Jest-to-Bun test migration: import replacements, mock rewrites, timer handling and config conversion, plus fixes for errors like Cannot find module '@jest/globals'.

Which mocks need rewriting?

jest.fn() turns into mock(), jest.mock('module') into mock.module('module', () => {...}), and jest.clearAllMocks() into a .mockClear() call per mock. spyOn moves over unchanged.

Source text

SKILL.md · MIT
---
name: bun-jest-migration
description: Use when migrating from Jest to Bun's test runner, import compatibility, mocks, and config.
metadata:
  version: "1.0.0"
license: MIT
---

# 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

© secondsky · MIT · secondsky/claude-skills

Similar skills

Everything for Testing and QA →

From the same set89

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor