
Bun Test
- 198 installs
- 4 repo stars
- Updated January 26, 2026
- daleseo/bun-skills
Write and run fast unit, integration, and snapshot tests with Bun's built-in test runner, mocks, and expect matchers in TypeScript/JavaScript projects.
About
bun-test from daleseo/bun-skills teaches Bun's first-class test runner for TypeScript and JavaScript: writing specs, using expect matchers, mocking dependencies, running focused suites locally, and wiring fast checks into ship pipelines for APIs, SaaS services, and CLI tools.
- Bun-native test runner and expect API
- Mocking, spies, and snapshot patterns
- Fast TS/JS unit and integration workflows
- CI-ready commands for pre-ship gates
- Practical patterns for backend and CLI repos
Bun Test by the numbers
- 198 all-time installs (skills.sh)
- Ranked #810 of 2,153 Testing & QA skills by installs in the Skillselion catalog
- Data as of Jul 29, 2026 (Skillselion catalog sync)
npx skills add https://github.com/daleseo/bun-skills --skill bun-testAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 198 |
|---|---|
| repo stars | ★ 4 |
| Last updated | January 26, 2026 |
| Repository | daleseo/bun-skills ↗ |
What it does
Write and run fast unit, integration, and snapshot tests with Bun's built-in test runner, mocks, and expect matchers in TypeScript/JavaScript projects.
Files
Bun Test Configuration
Set up Bun's built-in test runner with Jest-compatible APIs and significantly faster execution (3-10x faster than Jest).
Quick Reference
For detailed patterns, see:
- Jest Migration: jest-migration.md - Complete Jest to Bun migration guide
- Mocking: mocking.md - Mock functions, spies, module mocking
- Examples: examples.md - Test patterns for APIs, databases, async code
Core Workflow
1. Check Prerequisites
# Verify Bun installation
bun --version
# Check if project exists
ls -la package.json2. Determine Testing Needs
Ask the user what type of testing they need:
- Unit Testing: Test individual functions and modules
- Integration Testing: Test component interactions
- API Testing: Test HTTP endpoints
- Snapshot Testing: Test output consistency
3. Create Test Directory Structure
# Create test directories
mkdir -p tests/{unit,integration,fixtures}Recommended structure:
project/
├── src/
│ ├── utils.ts
│ └── components/
├── tests/
│ ├── unit/ # Unit tests
│ ├── integration/ # Integration tests
│ ├── fixtures/ # Test data
│ └── setup.ts # Global setup
├── package.json
└── bunfig.toml # Test configuration4. Configure Bun Test
Create bunfig.toml in project root:
[test]
# Preload files before running tests
preload = ["./tests/setup.ts"]
# Code coverage
coverage = true
coverageDir = "coverage"
coverageThreshold = 80
# Timeouts (in milliseconds)
timeout = 5000
# Bail after first failure
bail = false5. Create Test Setup File
Create tests/setup.ts:
import { beforeAll, afterAll, beforeEach, afterEach } from "bun:test";
// Global test setup
beforeAll(() => {
console.log("🧪 Starting test suite");
process.env.NODE_ENV = "test";
});
afterAll(() => {
console.log("✅ Test suite complete");
});
// Reset mocks before each test
beforeEach(() => {
// Clear mock state
});
afterEach(() => {
// Cleanup after each test
});
// Global test utilities
globalThis.testHelpers = {
wait: (ms: number) => new Promise(resolve => setTimeout(resolve, ms)),
};6. Write First Test
Create tests/unit/example.test.ts:
import { describe, it, expect, test } from "bun:test";
// Simple test
test("addition works", () => {
expect(1 + 1).toBe(2);
});
// Describe blocks for organization
describe("Array utilities", () => {
it("should filter even numbers", () => {
const numbers = [1, 2, 3, 4, 5, 6];
const evens = numbers.filter(n => n % 2 === 0);
expect(evens).toEqual([2, 4, 6]);
expect(evens).toHaveLength(3);
});
});
// Async tests
describe("Async operations", () => {
it("should handle promises", async () => {
const result = await Promise.resolve(42);
expect(result).toBe(42);
});
});For more test examples (API testing, database testing, etc.), see examples.md.
7. Add Mocking (If Needed)
import { describe, it, expect, mock, spyOn } from "bun:test";
describe("Mock functions", () => {
it("should create mock functions", () => {
const mockFn = mock((x: number) => x * 2);
const result = mockFn(5);
expect(result).toBe(10);
expect(mockFn).toHaveBeenCalledTimes(1);
expect(mockFn).toHaveBeenCalledWith(5);
});
it("should spy on methods", () => {
const obj = {
method: (x: number) => x * 2,
};
const spy = spyOn(obj, "method");
obj.method(5);
expect(spy).toHaveBeenCalledWith(5);
expect(spy).toHaveReturnedWith(10);
});
});For advanced mocking patterns, see mocking.md.
8. Update package.json
Add test scripts:
{
"scripts": {
"test": "bun test",
"test:watch": "bun test --watch",
"test:coverage": "bun test --coverage",
"test:ui": "bun test --coverage --reporter=html"
}
}9. Run Tests
# Run all tests
bun test
# Run specific file
bun test tests/unit/utils.test.ts
# Watch mode
bun test --watch
# With coverage
bun test --coverage
# Filter by name
bun test --test-name-pattern="should handle"Jest Migration
If migrating from Jest, see jest-migration.md for:
- Import updates (
@jest/globals→bun:test) - Mock syntax changes (
jest.fn()→mock()) - Configuration migration
- Compatibility notes
Key changes:
// Before (Jest)
import { describe, it, expect } from '@jest/globals';
const mockFn = jest.fn();
// After (Bun)
import { describe, it, expect, mock } from 'bun:test';
const mockFn = mock();Common Test Patterns
Testing Functions
import { test, expect } from "bun:test";
function add(a: number, b: number): number {
return a + b;
}
test("add function", () => {
expect(add(2, 3)).toBe(5);
expect(add(-1, 1)).toBe(0);
});Testing Errors
test("should throw errors", () => {
const throwError = () => {
throw new Error("Something went wrong");
};
expect(throwError).toThrow("Something went wrong");
expect(throwError).toThrow(Error);
});
test("should reject promises", async () => {
const asyncReject = async () => {
throw new Error("Async error");
};
await expect(asyncReject()).rejects.toThrow("Async error");
});Snapshot Testing
test("should match snapshot", () => {
const data = {
id: 1,
name: "Test User",
email: "test@example.com",
};
expect(data).toMatchSnapshot();
});
test("should match inline snapshot", () => {
const config = { theme: "dark", language: "en" };
expect(config).toMatchInlineSnapshot(`
{
"theme": "dark",
"language": "en"
}
`);
});Matchers Reference
Common matchers available:
// Equality
expect(value).toBe(expected); // ===
expect(value).toEqual(expected); // Deep equality
// Truthiness
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeDefined();
expect(value).toBeUndefined();
// Numbers
expect(number).toBeGreaterThan(3);
expect(number).toBeLessThan(5);
// Strings
expect(string).toMatch(/pattern/);
expect(string).toContain("substring");
// Arrays
expect(array).toContain(item);
expect(array).toHaveLength(3);
// Objects
expect(object).toHaveProperty("key");
expect(object).toMatchObject({ subset });
// Promises
await expect(promise).resolves.toBe(value);
await expect(promise).rejects.toThrow();
// Mock functions
expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledTimes(3);
expect(mockFn).toHaveBeenCalledWith(arg1, arg2);Test Organization
Setup and Teardown
import { beforeAll, afterAll, beforeEach, afterEach, describe, it } from "bun:test";
describe("User service", () => {
let db: Database;
beforeAll(async () => {
// Setup before all tests
db = await connectToDatabase();
});
afterAll(async () => {
// Cleanup after all tests
await db.close();
});
beforeEach(async () => {
// Reset before each test
await db.clear();
});
it("should create user", async () => {
const user = await db.users.create({ name: "Test" });
expect(user.id).toBeDefined();
});
});Coverage Configuration
View coverage report:
# Generate coverage
bun test --coverage
# View HTML report
bun test --coverage --reporter=html
open coverage/index.htmlSet coverage thresholds in bunfig.toml:
[test]
coverage = true
coverageThreshold = 80 # Fail if coverage < 80%Debugging Tests
# Run with debugger
bun test --inspect
# Verbose output
bun test --verbose
# Show all test results
bun test --reporter=tapPerformance
Bun test is significantly faster than Jest:
- Jest: ~15 seconds for 100 tests
- Bun: ~2 seconds for 100 tests
3-10x faster execution!
Completion Checklist
- ✅ Test directory structure created
- ✅ bunfig.toml configured
- ✅ Test setup file created
- ✅ Example tests written
- ✅ Package.json scripts updated
- ✅ Tests run successfully
- ✅ Coverage configured (if needed)
Next Steps
After basic setup:
1. Write tests: Add tests for critical business logic 2. CI/CD: Configure tests to run in your pipeline 3. Coverage: Set up coverage reporting 4. Pre-commit: Add pre-commit hooks to run tests 5. Documentation: Document testing patterns for the team
For detailed implementations, see the reference files linked above.
Test Examples and Patterns
Comprehensive test examples for common scenarios with Bun's test runner.
Unit Tests
Testing Functions
import { describe, it, expect } from "bun:test";
// utils.ts
export function add(a: number, b: number): number {
return a + b;
}
export function multiply(a: number, b: number): number {
return a * b;
}
// utils.test.ts
describe("Math utilities", () => {
it("should add numbers", () => {
expect(add(2, 3)).toBe(5);
expect(add(-1, 1)).toBe(0);
});
it("should multiply numbers", () => {
expect(multiply(2, 3)).toBe(6);
expect(multiply(0, 5)).toBe(0);
});
});Testing Classes
class Calculator {
private history: number[] = [];
add(a: number, b: number): number {
const result = a + b;
this.history.push(result);
return result;
}
getHistory(): number[] {
return [...this.history];
}
}
describe("Calculator", () => {
it("should calculate and store history", () => {
const calc = new Calculator();
expect(calc.add(2, 3)).toBe(5);
expect(calc.add(10, 5)).toBe(15);
expect(calc.getHistory()).toEqual([5, 15]);
});
});Async Tests
Testing Promises
async function fetchData(url: string): Promise<any> {
const response = await fetch(url);
return response.json();
}
it("should fetch data", async () => {
const data = await fetchData("https://api.example.com/data");
expect(data).toBeDefined();
});
it("should handle errors", async () => {
await expect(
fetchData("https://api.example.com/invalid")
).rejects.toThrow();
});Testing Async/Await
describe("Async operations", () => {
it("should wait for async function", async () => {
const result = await new Promise((resolve) => {
setTimeout(() => resolve("done"), 100);
});
expect(result).toBe("done");
});
it("should handle async errors", async () => {
const asyncError = async () => {
throw new Error("Async error");
};
await expect(asyncError()).rejects.toThrow("Async error");
});
});Snapshot Testing
Component Snapshots
import { test, expect } from "bun:test";
test("should match component snapshot", () => {
const component = {
type: "button",
props: {
onClick: () => {},
children: "Click me",
},
};
expect(component).toMatchSnapshot();
});Inline Snapshots
test("should match inline snapshot", () => {
const config = {
theme: "dark",
language: "en",
};
expect(config).toMatchInlineSnapshot(`
{
"theme": "dark",
"language": "en"
}
`);
});API Testing
REST API Tests
describe("User API", () => {
it("should GET users", async () => {
const response = await fetch("http://localhost:3000/api/users");
const users = await response.json();
expect(response.status).toBe(200);
expect(users).toBeArray();
});
it("should POST new user", async () => {
const newUser = {
name: "Test User",
email: "test@example.com",
};
const response = await fetch("http://localhost:3000/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(newUser),
});
const created = await response.json();
expect(response.status).toBe(201);
expect(created).toMatchObject(newUser);
});
});GraphQL Tests
it("should query GraphQL API", async () => {
const query = `
query {
user(id: 1) {
name
email
}
}
`;
const response = await fetch("http://localhost:3000/graphql", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query }),
});
const result = await response.json();
expect(result.data.user).toBeDefined();
expect(result.data.user.name).toBeTruthy();
});Database Testing
With Setup and Teardown
import { beforeAll, afterAll, beforeEach, describe, it, expect } from "bun:test";
let db: Database;
beforeAll(async () => {
// Setup database connection
db = await connectToDatabase();
});
afterAll(async () => {
// Close database connection
await db.close();
});
beforeEach(async () => {
// Clear test data
await db.query("DELETE FROM users WHERE email LIKE '%@test.com'");
});
describe("User repository", () => {
it("should create user", async () => {
const user = await db.users.create({
name: "Test User",
email: "test@test.com",
});
expect(user.id).toBeDefined();
expect(user.name).toBe("Test User");
});
it("should find user by email", async () => {
await db.users.create({
name: "Test User",
email: "find@test.com",
});
const found = await db.users.findByEmail("find@test.com");
expect(found).toBeDefined();
expect(found.name).toBe("Test User");
});
});Error Handling
Testing Exceptions
describe("Error handling", () => {
it("should throw for invalid input", () => {
const divide = (a: number, b: number) => {
if (b === 0) throw new Error("Division by zero");
return a / b;
};
expect(() => divide(10, 0)).toThrow("Division by zero");
expect(() => divide(10, 0)).toThrow(Error);
});
it("should reject promise", async () => {
const asyncReject = async () => {
throw new Error("Rejected");
};
await expect(asyncReject()).rejects.toThrow("Rejected");
});
});Matchers Reference
Common Matchers
// Equality
expect(value).toBe(expected); // ===
expect(value).toEqual(expected); // Deep equality
expect(value).toStrictEqual(expected); // Strict deep equality
// Truthiness
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeNull();
expect(value).toBeUndefined();
expect(value).toBeDefined();
// Numbers
expect(number).toBeGreaterThan(3);
expect(number).toBeGreaterThanOrEqual(3);
expect(number).toBeLessThan(5);
expect(number).toBeLessThanOrEqual(5);
expect(number).toBeCloseTo(0.3, 5); // Floating point
// Strings
expect(string).toMatch(/pattern/);
expect(string).toContain("substring");
// Arrays
expect(array).toContain(item);
expect(array).toHaveLength(3);
expect(array).toBeArray();
// Objects
expect(object).toHaveProperty("key");
expect(object).toHaveProperty("key", value);
expect(object).toMatchObject({ subset });
// Functions
expect(fn).toThrow();
expect(fn).toThrow(Error);
expect(fn).toThrow("message");
// Promises
await expect(promise).resolves.toBe(value);
await expect(promise).rejects.toThrow();
// Mock functions
expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledTimes(3);
expect(mockFn).toHaveBeenCalledWith(arg1, arg2);
expect(mockFn).toHaveReturnedWith(value);Test Organization
Nested Describes
describe("User management", () => {
describe("Authentication", () => {
it("should login with valid credentials", () => {
// test
});
it("should reject invalid credentials", () => {
// test
});
});
describe("Authorization", () => {
it("should allow admin access", () => {
// test
});
it("should deny regular user access", () => {
// test
});
});
});Test Isolation
describe("Isolated tests", () => {
let counter: number;
beforeEach(() => {
counter = 0; // Reset before each test
});
it("should increment counter", () => {
counter++;
expect(counter).toBe(1);
});
it("should start from zero", () => {
expect(counter).toBe(0); // Isolated from previous test
});
});Performance Testing
Timing Tests
it("should complete within time limit", async () => {
const start = performance.now();
await someOperation();
const duration = performance.now() - start;
expect(duration).toBeLessThan(1000); // Must complete within 1 second
});Load Testing
it("should handle multiple concurrent requests", async () => {
const requests = Array.from({ length: 100 }, (_, i) =>
fetch(`http://localhost:3000/api/data/${i}`)
);
const responses = await Promise.all(requests);
expect(responses.every((r) => r.ok)).toBe(true);
});Migrating from Jest to Bun Test
Complete guide for migrating Jest test suites to Bun's built-in test runner.
API Compatibility
Bun test provides Jest-compatible APIs for seamless migration:
| Jest API | Bun Test | Status |
|---|---|---|
describe, it, test | ✅ Identical | Fully supported |
expect with matchers | ✅ Identical | Most matchers supported |
beforeAll, afterAll | ✅ Identical | Fully supported |
beforeEach, afterEach | ✅ Identical | Fully supported |
jest.fn() | mock() | Different import |
jest.spyOn() | spyOn() | Different import |
| Snapshot testing | ✅ Identical | Fully supported |
| Async testing | ✅ Identical | Fully supported |
Migration Steps
1. Update Imports
Before (Jest):
import { describe, it, expect } from '@jest/globals';
import { jest } from '@jest/globals';After (Bun):
import { describe, it, expect, mock, spyOn } from 'bun:test';2. Update Mock Syntax
Before (Jest):
const mockFn = jest.fn();
jest.fn((x) => x * 2);
jest.spyOn(obj, 'method');After (Bun):
const mockFn = mock();
mock((x) => x * 2);
spyOn(obj, 'method');3. Update Configuration
Remove Jest config files:
rm jest.config.js
rm jest.setup.jsCreate bunfig.toml:
[test]
preload = ["./tests/setup.ts"]
coverage = true
coverageDir = "coverage"
coverageThreshold = 80
timeout = 50004. Update package.json
Before:
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
},
"devDependencies": {
"jest": "^29.0.0",
"@types/jest": "^29.0.0"
}
}After:
{
"scripts": {
"test": "bun test",
"test:watch": "bun test --watch",
"test:coverage": "bun test --coverage"
}
}Remove Jest dependencies:
bun remove jest @types/jest ts-jestJest Features Not Yet Supported
Module Mocking
Jest's jest.mock() for entire modules has limited support:
Jest (advanced mocking):
jest.mock('./api', () => ({
fetchUser: jest.fn(),
}));Workaround in Bun:
// Use dependency injection or manual mocking
import { mock } from 'bun:test';
const mockFetchUser = mock();
const api = { fetchUser: mockFetchUser };Fake Timers
Jest's jest.useFakeTimers() is not yet available:
Workaround:
// Use manual time control
let currentTime = Date.now();
const originalDateNow = Date.now;
beforeEach(() => {
Date.now = () => currentTime;
});
afterEach(() => {
Date.now = originalDateNow;
});Performance Comparison
Bun test is significantly faster:
# Jest
npm test # ~15 seconds for 100 tests
# Bun
bun test # ~2 seconds for 100 tests7-10x faster execution!
Migration Checklist
- [ ] Update imports (
@jest/globals→bun:test) - [ ] Replace
jest.fn()withmock() - [ ] Replace
jest.spyOn()withspyOn() - [ ] Remove Jest config files
- [ ] Create
bunfig.toml - [ ] Update package.json scripts
- [ ] Remove Jest dependencies
- [ ] Run tests to verify
- [ ] Update CI/CD pipelines
- [ ] Update documentation
Mocking Patterns in Bun Test
Complete guide to mocking functions, modules, and objects in Bun's test runner.
Mock Functions
Basic Mock
import { describe, it, expect, mock } from "bun:test";
describe("Mock functions", () => {
it("should create mock functions", () => {
const mockFn = mock((x: number) => x * 2);
const result = mockFn(5);
expect(result).toBe(10);
expect(mockFn).toHaveBeenCalledTimes(1);
expect(mockFn).toHaveBeenCalledWith(5);
});
});Mock Return Values
it("should implement mock return values", () => {
const mockFn = mock(() => "default");
mockFn.mockReturnValue("mocked");
expect(mockFn()).toBe("mocked");
});
it("should implement mock return value once", () => {
const mockFn = mock();
mockFn.mockReturnValueOnce("first");
mockFn.mockReturnValueOnce("second");
mockFn.mockReturnValue("default");
expect(mockFn()).toBe("first");
expect(mockFn()).toBe("second");
expect(mockFn()).toBe("default");
});Mock Implementations
it("should implement mock implementations", () => {
const mockFn = mock();
mockFn.mockImplementation((x: number) => x + 1);
expect(mockFn(5)).toBe(6);
});
it("should implement mock implementation once", () => {
const mockFn = mock();
mockFn.mockImplementationOnce((x) => x * 2);
mockFn.mockImplementation((x) => x * 3);
expect(mockFn(5)).toBe(10);
expect(mockFn(5)).toBe(15);
});Tracking Calls
it("should track mock calls", () => {
const mockFn = mock();
mockFn(1, 2);
mockFn(3, 4);
expect(mockFn.mock.calls).toEqual([[1, 2], [3, 4]]);
expect(mockFn.mock.calls[0]).toEqual([1, 2]);
expect(mockFn.mock.calls.length).toBe(2);
});
it("should track return values", () => {
const mockFn = mock((x) => x * 2);
mockFn(5);
mockFn(10);
expect(mockFn.mock.results).toEqual([
{ type: 'return', value: 10 },
{ type: 'return', value: 20 }
]);
});Spying on Methods
Basic Spy
import { spyOn } from "bun:test";
it("should spy on object methods", () => {
const obj = {
method: (x: number) => x * 2,
};
const spy = spyOn(obj, "method");
obj.method(5);
expect(spy).toHaveBeenCalledWith(5);
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveReturnedWith(10);
});Spy Implementation
it("should override spy implementation", () => {
const obj = {
method: (x: number) => x * 2,
};
const spy = spyOn(obj, "method").mockImplementation((x) => x * 3);
expect(obj.method(5)).toBe(15);
expect(spy).toHaveBeenCalled();
});Restore Spy
it("should restore original implementation", () => {
const obj = {
method: (x: number) => x * 2,
};
const spy = spyOn(obj, "method").mockImplementation((x) => x * 3);
expect(obj.method(5)).toBe(15);
spy.mockRestore();
expect(obj.method(5)).toBe(10); // Original implementation
});Module Mocking
Manual Module Mocks
// Create a manual mock
const mockFetchUser = mock(() => ({ id: 1, name: "Test User" }));
const mockCreateUser = mock(() => ({ success: true }));
const api = {
fetchUser: mockFetchUser,
createUser: mockCreateUser,
};
it("should use mocked module", async () => {
const user = await api.fetchUser(1);
expect(user).toEqual({ id: 1, name: "Test User" });
expect(mockFetchUser).toHaveBeenCalledWith(1);
});Dependency Injection
// api.ts
export interface ApiClient {
fetchUser(id: number): Promise<User>;
}
export class UserService {
constructor(private api: ApiClient) {}
async getUser(id: number) {
return this.api.fetchUser(id);
}
}
// test
it("should use injected dependency", async () => {
const mockApi: ApiClient = {
fetchUser: mock(() => Promise.resolve({ id: 1, name: "Test" })),
};
const service = new UserService(mockApi);
const user = await service.getUser(1);
expect(user.name).toBe("Test");
});Async Mocks
Promise Mocks
it("should mock promises", async () => {
const mockFn = mock(() => Promise.resolve("data"));
const result = await mockFn();
expect(result).toBe("data");
expect(mockFn).toHaveBeenCalled();
});
it("should mock rejected promises", async () => {
const mockFn = mock(() => Promise.reject(new Error("Failed")));
await expect(mockFn()).rejects.toThrow("Failed");
});Async Function Mocks
it("should mock async functions", async () => {
const mockFn = mock(async (x: number) => {
await new Promise(resolve => setTimeout(resolve, 10));
return x * 2;
});
const result = await mockFn(5);
expect(result).toBe(10);
});Clearing and Resetting Mocks
Clear Mock Calls
it("should clear mock history", () => {
const mockFn = mock();
mockFn(1);
expect(mockFn).toHaveBeenCalledTimes(1);
mockFn.mockClear();
expect(mockFn).toHaveBeenCalledTimes(0);
mockFn(2);
expect(mockFn).toHaveBeenCalledTimes(1);
});Reset Mocks
it("should reset mock implementation", () => {
const mockFn = mock(() => "initial");
mockFn.mockReturnValue("changed");
expect(mockFn()).toBe("changed");
mockFn.mockReset();
expect(mockFn()).toBeUndefined(); // Back to no implementation
});Global Mock Management
import { beforeEach, afterEach } from "bun:test";
beforeEach(() => {
// Clear all mocks before each test
});
afterEach(() => {
// Restore all mocks after each test
});Advanced Patterns
Partial Mocks
const obj = {
method1: (x) => x * 2,
method2: (x) => x * 3,
};
const spy1 = spyOn(obj, "method1");
// method2 remains unmocked
expect(obj.method1(5)).toBeDefined(); // Mocked
expect(obj.method2(5)).toBe(15); // OriginalConditional Mocks
it("should mock conditionally", () => {
const mockFn = mock((x: number) => {
if (x > 10) return "large";
return "small";
});
expect(mockFn(5)).toBe("small");
expect(mockFn(15)).toBe("large");
});Chaining Mock Calls
it("should chain mock methods", () => {
const mockFn = mock()
.mockReturnValueOnce("first")
.mockReturnValueOnce("second")
.mockReturnValue("default");
expect(mockFn()).toBe("first");
expect(mockFn()).toBe("second");
expect(mockFn()).toBe("default");
});Testing with Mocks
API Mocking
it("should mock API calls", async () => {
const mockFetch = mock(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ data: "test" }),
})
);
global.fetch = mockFetch as any;
const response = await fetch("/api/data");
const data = await response.json();
expect(data).toEqual({ data: "test" });
expect(mockFetch).toHaveBeenCalledWith("/api/data");
});Timer Mocking
it("should test delayed operations", async () => {
const callback = mock();
setTimeout(callback, 1000);
// Wait for timer
await new Promise(resolve => setTimeout(resolve, 1100));
expect(callback).toHaveBeenCalled();
});Best Practices
1. Clear mocks between tests: Use beforeEach to reset state 2. Mock at boundaries: Mock external dependencies, not internal functions 3. Verify behavior: Use .toHaveBeenCalledWith() to ensure correct arguments 4. Don't over-mock: Only mock what's necessary for the test 5. Use type-safe mocks: Leverage TypeScript for mock definitions
Common Patterns
Database Mock
const mockDb = {
query: mock((sql: string) => Promise.resolve([])),
insert: mock((table: string, data: any) => Promise.resolve({ id: 1 })),
};Logger Mock
const mockLogger = {
info: mock(),
warn: mock(),
error: mock(),
};File System Mock
const mockFs = {
readFile: mock(() => Promise.resolve("file contents")),
writeFile: mock(() => Promise.resolve()),
};