with one click
bun-test-runner
// Fast test execution with Bun's built-in test runner including snapshot testing, mocking, code coverage, lifecycle hooks, DOM testing with happy-dom, and migration from Jest and Vitest to Bun test.
// Fast test execution with Bun's built-in test runner including snapshot testing, mocking, code coverage, lifecycle hooks, DOM testing with happy-dom, and migration from Jest and Vitest to Bun test.
| name | Bun Test Runner |
| description | Fast test execution with Bun's built-in test runner including snapshot testing, mocking, code coverage, lifecycle hooks, DOM testing with happy-dom, and migration from Jest and Vitest to Bun test. |
| version | 1.0.0 |
| author | thetestingacademy |
| license | MIT |
| tags | ["bun","bun-test","test-runner","snapshot-testing","mocking","code-coverage","happy-dom","fast-testing","runtime","javascript-runtime"] |
| testingTypes | ["unit","integration"] |
| frameworks | ["bun"] |
| languages | ["typescript","javascript"] |
| domains | ["backend","web","api"] |
| agents | ["claude-code","cursor","github-copilot","windsurf","codex","aider","continue","cline","zed","bolt","gemini-cli","amp"] |
You are an expert in Bun's built-in test runner. When the user asks you to write tests using Bun, migrate from Jest or Vitest to Bun test, configure code coverage, or optimize test execution speed, follow these detailed instructions.
src/
utils/
math.ts
math.test.ts
string.ts
string.test.ts
services/
user-service.ts
user-service.test.ts
api-client.ts
api-client.test.ts
db/
queries.ts
queries.test.ts
__snapshots__/
.gitkeep
bunfig.toml
package.json
# bunfig.toml
[test]
# Test file patterns
root = "./src"
# Coverage configuration
coverage = true
coverageReporter = ["text", "lcov"]
coverageThreshold = { line = 80, function = 80, statement = 80 }
# Preload scripts
preload = ["./test-setup.ts"]
# Timeout per test (ms)
timeout = 5000
# Bail after N failures (0 = no bail)
bail = 0
// src/utils/math.test.ts
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
import { add, multiply, divide, fibonacci, isPrime } from './math';
describe('Math Utilities', () => {
describe('add', () => {
it('should add two positive numbers', () => {
expect(add(2, 3)).toBe(5);
});
it('should handle negative numbers', () => {
expect(add(-1, -2)).toBe(-3);
expect(add(-1, 5)).toBe(4);
});
it('should handle zero', () => {
expect(add(0, 0)).toBe(0);
expect(add(5, 0)).toBe(5);
});
it('should handle floating point', () => {
expect(add(0.1, 0.2)).toBeCloseTo(0.3);
});
});
describe('divide', () => {
it('should divide two numbers', () => {
expect(divide(10, 2)).toBe(5);
});
it('should throw on division by zero', () => {
expect(() => divide(10, 0)).toThrow('Division by zero');
});
it('should handle decimal results', () => {
expect(divide(1, 3)).toBeCloseTo(0.333, 2);
});
});
describe('fibonacci', () => {
it('should return correct values for small inputs', () => {
expect(fibonacci(0)).toBe(0);
expect(fibonacci(1)).toBe(1);
expect(fibonacci(2)).toBe(1);
expect(fibonacci(10)).toBe(55);
});
it('should throw for negative inputs', () => {
expect(() => fibonacci(-1)).toThrow();
});
});
describe('isPrime', () => {
it.each([2, 3, 5, 7, 11, 13])('should identify %d as prime', (n) => {
expect(isPrime(n)).toBe(true);
});
it.each([0, 1, 4, 6, 8, 9, 10])('should identify %d as not prime', (n) => {
expect(isPrime(n)).toBe(false);
});
});
});
// src/services/user-service.test.ts
import { describe, it, expect, mock, spyOn, beforeEach } from 'bun:test';
import { UserService } from './user-service';
import { db } from '../db/connection';
// Mock the entire module
mock.module('../db/connection', () => ({
db: {
query: mock(() => Promise.resolve([])),
insert: mock(() => Promise.resolve({ id: '123' })),
update: mock(() => Promise.resolve({ affected: 1 })),
delete: mock(() => Promise.resolve({ affected: 1 })),
},
}));
describe('UserService', () => {
let service: UserService;
beforeEach(() => {
service = new UserService();
// Reset all mocks
(db.query as any).mockClear();
(db.insert as any).mockClear();
});
it('should find a user by ID', async () => {
const mockUser = { id: '123', name: 'Alice', email: 'alice@test.com' };
(db.query as any).mockResolvedValueOnce([mockUser]);
const user = await service.findById('123');
expect(user).toEqual(mockUser);
expect(db.query).toHaveBeenCalledTimes(1);
});
it('should return null for non-existent user', async () => {
(db.query as any).mockResolvedValueOnce([]);
const user = await service.findById('nonexistent');
expect(user).toBeNull();
});
it('should create a new user', async () => {
const newUser = { name: 'Bob', email: 'bob@test.com' };
(db.insert as any).mockResolvedValueOnce({ id: '456', ...newUser });
const created = await service.create(newUser);
expect(created.id).toBe('456');
expect(db.insert).toHaveBeenCalledTimes(1);
});
it('should throw on duplicate email', async () => {
(db.insert as any).mockRejectedValueOnce(new Error('UNIQUE constraint failed'));
expect(service.create({ name: 'Bob', email: 'existing@test.com' })).rejects.toThrow(
'Email already exists'
);
});
});
// src/services/api-client.test.ts
import { describe, it, expect, spyOn, mock, beforeEach, afterEach } from 'bun:test';
import { ApiClient } from './api-client';
describe('ApiClient', () => {
let client: ApiClient;
let fetchSpy: any;
beforeEach(() => {
client = new ApiClient('https://api.example.com');
fetchSpy = spyOn(globalThis, 'fetch');
});
afterEach(() => {
fetchSpy.mockRestore();
});
it('should make GET requests', async () => {
fetchSpy.mockResolvedValueOnce(
new Response(JSON.stringify({ data: 'test' }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
})
);
const result = await client.get('/users');
expect(result).toEqual({ data: 'test' });
expect(fetchSpy).toHaveBeenCalledWith(
'https://api.example.com/users',
expect.objectContaining({ method: 'GET' })
);
});
it('should handle network errors', async () => {
fetchSpy.mockRejectedValueOnce(new Error('Network error'));
expect(client.get('/users')).rejects.toThrow('Network error');
});
it('should retry on 5xx errors', async () => {
fetchSpy
.mockResolvedValueOnce(new Response('', { status: 503 }))
.mockResolvedValueOnce(new Response('', { status: 503 }))
.mockResolvedValueOnce(
new Response(JSON.stringify({ data: 'ok' }), { status: 200 })
);
const result = await client.get('/users', { retries: 3 });
expect(result).toEqual({ data: 'ok' });
expect(fetchSpy).toHaveBeenCalledTimes(3);
});
it('should include authorization header', async () => {
fetchSpy.mockResolvedValueOnce(
new Response(JSON.stringify({}), { status: 200 })
);
client.setToken('test-token');
await client.get('/protected');
expect(fetchSpy).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
headers: expect.objectContaining({
Authorization: 'Bearer test-token',
}),
})
);
});
});
// src/utils/string.test.ts
import { describe, it, expect } from 'bun:test';
import { formatDate, slugify, truncate, parseMarkdown } from './string';
describe('String Utilities', () => {
describe('slugify', () => {
it('should create URL-safe slugs', () => {
expect(slugify('Hello World')).toBe('hello-world');
expect(slugify(' Multiple Spaces ')).toBe('multiple-spaces');
expect(slugify('Special @#$ Characters!')).toBe('special-characters');
expect(slugify('CamelCaseTitle')).toBe('camelcasetitle');
});
it('should match snapshot', () => {
const testCases = [
'Hello World',
'TypeScript Testing',
'API v2.0 Documentation',
'user@email.com',
].map((input) => ({ input, output: slugify(input) }));
expect(testCases).toMatchSnapshot();
});
});
describe('parseMarkdown', () => {
it('should parse headings', () => {
expect(parseMarkdown('# Title')).toMatchSnapshot();
});
it('should parse lists', () => {
const input = '- Item 1\n- Item 2\n- Item 3';
expect(parseMarkdown(input)).toMatchSnapshot();
});
it('should parse code blocks', () => {
const input = '```typescript\nconst x = 1;\n```';
expect(parseMarkdown(input)).toMatchSnapshot();
});
});
});
// test-setup.ts
import { GlobalRegistrator } from '@happy-dom/global-registrator';
GlobalRegistrator.register();
// src/components/counter.test.ts
import { describe, it, expect, beforeEach } from 'bun:test';
describe('Counter Component (DOM)', () => {
beforeEach(() => {
document.body.innerHTML = '';
});
it('should render counter with initial value', () => {
document.body.innerHTML = `
<div id="counter">
<span class="count">0</span>
<button class="increment">+</button>
<button class="decrement">-</button>
</div>
`;
const count = document.querySelector('.count');
expect(count?.textContent).toBe('0');
});
it('should increment counter on button click', () => {
document.body.innerHTML = `<button id="btn" onclick="this.dataset.count = (parseInt(this.dataset.count || '0') + 1)">Click</button>`;
const btn = document.getElementById('btn') as HTMLButtonElement;
btn.click();
expect(btn.dataset.count).toBe('1');
});
});
# Run all tests
bun test
# Run specific file
bun test src/utils/math.test.ts
# Run tests matching pattern
bun test --grep "should add"
# Run with coverage
bun test --coverage
# Run in watch mode
bun test --watch
# Run with timeout
bun test --timeout 10000
# Bail on first failure
bun test --bail 1
# Run specific directory
bun test src/services/
// src/services/api-integration.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'bun:test';
import { createServer } from '../server';
describe('API Integration Tests', () => {
let server: any;
let baseUrl: string;
beforeAll(async () => {
server = createServer({ port: 0 }); // Random port
const address = server.address();
baseUrl = \`http://localhost:\${address.port}\`;
});
afterAll(() => {
server.close();
});
it('should handle POST /api/users', async () => {
const response = await fetch(\`\${baseUrl}/api/users\`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Test User', email: 'test@bun.sh' }),
});
expect(response.status).toBe(201);
const data = await response.json();
expect(data.id).toBeDefined();
expect(data.name).toBe('Test User');
});
it('should return 400 for invalid input', async () => {
const response = await fetch(\`\${baseUrl}/api/users\`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: '' }),
});
expect(response.status).toBe(400);
});
it('should handle GET /api/users/:id', async () => {
// Create a user first
const createResponse = await fetch(\`\${baseUrl}/api/users\`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Lookup User', email: 'lookup@bun.sh' }),
});
const created = await createResponse.json();
// Fetch the user
const response = await fetch(\`\${baseUrl}/api/users/\${created.id}\`);
expect(response.status).toBe(200);
const data = await response.json();
expect(data.name).toBe('Lookup User');
});
it('should return 404 for non-existent user', async () => {
const response = await fetch(\`\${baseUrl}/api/users/nonexistent\`);
expect(response.status).toBe(404);
});
});
// src/utils/file-processor.test.ts
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
import { mkdirSync, rmSync, writeFileSync } from 'fs';
import { join } from 'path';
import { processFiles, readConfig } from './file-processor';
describe('File Processor', () => {
const testDir = join(import.meta.dir, '__test_tmp__');
beforeEach(() => {
mkdirSync(testDir, { recursive: true });
});
afterEach(() => {
rmSync(testDir, { recursive: true, force: true });
});
it('should process markdown files in directory', async () => {
writeFileSync(join(testDir, 'test1.md'), '# Hello');
writeFileSync(join(testDir, 'test2.md'), '# World');
writeFileSync(join(testDir, 'ignore.txt'), 'not markdown');
const results = await processFiles(testDir, '*.md');
expect(results).toHaveLength(2);
expect(results[0].content).toContain('Hello');
});
it('should read YAML config files', async () => {
writeFileSync(join(testDir, 'config.yaml'), 'name: test\\nversion: 1.0.0');
const config = await readConfig(join(testDir, 'config.yaml'));
expect(config.name).toBe('test');
expect(config.version).toBe('1.0.0');
});
it('should handle empty directories', async () => {
const results = await processFiles(testDir, '*.md');
expect(results).toHaveLength(0);
});
});
Pull weekly/monthly performance metrics for qaskills.sh from Google Analytics 4 (GA4) and Google Search Console (GSC) via Claude in Chrome. Returns traffic, channels, top queries, top pages, growth deltas in a single report.
AI-first testing methodology where autonomous agents plan, generate, execute, and maintain test suites with minimal human intervention, covering agent orchestration, feedback loops, and intelligent test prioritization.
Comprehensive evaluation patterns for AI agents including multi-turn conversation testing, LLM-as-judge frameworks, benchmark suites, regression detection, and systematic eval pipelines for measuring agent quality and safety.
Systematic patterns for prompting AI coding agents to generate high-quality tests including prompt engineering for test creation, coverage-driven generation, mutation-aware testing, and review checklists for AI-generated test code.
Comprehensive API security testing based on OWASP API Security Top 10 including broken authentication, injection attacks, rate limiting, BOLA/BFLA vulnerabilities, and automated security scanning with ZAP and custom scripts.
Cloud-based cross-browser and cross-device testing with BrowserStack including Automate, App Automate, Percy visual testing, Observability, and integration with Playwright, Selenium, and CI/CD pipelines.