typescript-testing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTypeScript Testing with Bun
使用Bun进行TypeScript测试
TypeScript/JavaScript-specific testing patterns and best practices using Bun's built-in test runner, complementing general testing-workflow skill.
使用Bun内置测试运行器的TypeScript/JavaScript专属测试模式与最佳实践,作为通用测试工作流技能的补充。
CRITICAL: Bun Test Execution
重要提示:Bun测试执行
NEVER use jest, vitest, or other test runners in Bun projects:
bash
undefined绝不要在Bun项目中使用jest、vitest或其他测试运行器:
bash
undefined✅ CORRECT - Bun test execution
✅ 正确 - Bun测试执行
bun test
bun test --watch
bun test src/tests
bun test --coverage
bun test --bail
bun test tests/unit.test.ts
bun test
bun test --watch
bun test src/tests
bun test --coverage
bun test --bail
bun test tests/unit.test.ts
❌ WRONG - Never use jest in Bun projects
❌ 错误 - 绝不要在Bun项目中使用jest
❌ jest
❌ jest
❌ jest --watch
❌ jest --watch
❌ npm run test (if mapped to jest)
❌ npm run test (如果映射到jest)
❌ WRONG - Never use vitest in Bun projects
❌ 错误 - 绝不要在Bun项目中使用vitest
❌ vitest
❌ vitest
❌ vitest run
❌ vitest run
**Always use `bun test` directly** (never use jest/vitest in Bun projects).
---
**始终直接使用`bun test`**(绝不要在Bun项目中使用jest/vitest)。
---Test File Organization
测试文件组织
File Naming Conventions
文件命名规范
Bun recognizes test files by standard conventions:
src/
├── utils/
│ ├── math.ts
│ ├── math.test.ts # ✅ Standard .test.ts
│ ├── string-utils.spec.ts # ✅ Alternative .spec.ts
│ └── validation/
│ ├── validator.ts
│ └── validator.test.ts
├── services/
│ ├── api.ts
│ └── __tests__/ # ✅ __tests__ directory
│ └── api.test.ts
└── components/
├── Button.tsx
└── Button.test.tsx # ✅ React component testsBun通过标准规范识别测试文件:
src/
├── utils/
│ ├── math.ts
│ ├── math.test.ts # ✅ 标准.test.ts格式
│ ├── string-utils.spec.ts # ✅ 替代.spec.ts格式
│ └── validation/
│ ├── validator.ts
│ └── validator.test.ts
├── services/
│ ├── api.ts
│ └── __tests__/ # ✅ __tests__目录
│ └── api.test.ts
└── components/
├── Button.tsx
└── Button.test.tsx # ✅ React组件测试Discovery Patterns
自动发现规则
Bun automatically finds tests matching:
- /
*.test.ts*.test.tsx - /
*.test.js*.test.jsx - /
*.spec.ts*.spec.tsx - /
*.spec.js*.spec.jsx - Files in directories
__tests__
Bun会自动匹配以下测试文件:
- /
*.test.ts*.test.tsx - /
*.test.js*.test.jsx - /
*.spec.ts*.spec.tsx - /
*.spec.js*.spec.jsx - 目录下的文件
__tests__
Basic Test Structure
基础测试结构
Simple Test Example
简单测试示例
typescript
import { describe, it, expect } from "bun:test";
import { add, multiply } from "./math";
describe("Math utilities", () => {
it("should add two numbers", () => {
expect(add(2, 3)).toBe(5);
});
it("should multiply two numbers", () => {
expect(multiply(4, 5)).toBe(20);
});
it("should handle negative numbers", () => {
expect(add(-1, -2)).toBe(-3);
});
});typescript
import { describe, it, expect } from "bun:test";
import { add, multiply } from "./math";
describe("数学工具函数", () => {
it("应该能对两个数字求和", () => {
expect(add(2, 3)).toBe(5);
});
it("应该能对两个数字求积", () => {
expect(multiply(4, 5)).toBe(20);
});
it("应该能处理负数", () => {
expect(add(-1, -2)).toBe(-3);
});
});Describe Blocks
描述块嵌套
Organize tests with nested describe blocks:
typescript
import { describe, it, expect } from "bun:test";
import { UserService } from "./user-service";
describe("UserService", () => {
describe("create", () => {
it("should create user with valid data", () => {
// Test implementation
});
it("should throw error on invalid email", () => {
// Test implementation
});
});
describe("update", () => {
it("should update user properties", () => {
// Test implementation
});
it("should not update protected fields", () => {
// Test implementation
});
});
describe("delete", () => {
it("should delete user by id", () => {
// Test implementation
});
});
});使用嵌套describe块组织测试:
typescript
import { describe, it, expect } from "bun:test";
import { UserService } from "./user-service";
describe("UserService", () => {
describe("create方法", () => {
it("应该能使用有效数据创建用户", () => {
// 测试实现
});
it("在邮箱无效时应该抛出错误", () => {
// 测试实现
});
});
describe("update方法", () => {
it("应该能更新用户属性", () => {
// 测试实现
});
it("不应该更新受保护字段", () => {
// 测试实现
});
});
describe("delete方法", () => {
it("应该能通过ID删除用户", () => {
// 测试实现
});
});
});Bun Test API
Bun测试API
Describe and It
Describe与It
typescript
import { describe, it, expect } from "bun:test";
describe("Feature name", () => {
it("should do something", () => {
expect(true).toBe(true);
});
it("should handle edge case", () => {
expect(() => riskyOperation()).toThrow();
});
});typescript
import { describe, it, expect } from "bun:test";
describe("功能名称", () => {
it("应该完成某件事", () => {
expect(true).toBe(true);
});
it("应该处理边界情况", () => {
expect(() => riskyOperation()).toThrow();
});
});Common Assertions
常用断言
typescript
import { expect } from "bun:test";
// Equality
expect(value).toBe(5); // Strict equality (===)
expect(obj).toEqual({ a: 1 }); // Deep equality
expect(value).toStrictEqual(5); // Strict deep equality
// Truthiness
expect(value).toBeTruthy(); // Truthy value
expect(value).toBeFalsy(); // Falsy value
expect(value).toBeNull(); // null
expect(value).toBeUndefined(); // undefined
expect(value).toBeDefined(); // Not undefined
// Numbers
expect(number).toBeGreaterThan(5);
expect(number).toBeGreaterThanOrEqual(5);
expect(number).toBeLessThan(10);
expect(number).toBeLessThanOrEqual(10);
expect(0.1 + 0.2).toBeCloseTo(0.3); // Float comparison
// Strings
expect(string).toMatch(/pattern/);
expect(string).toContain("substring");
// Arrays
expect(array).toContain(value);
expect(array).toHaveLength(3);
// Objects
expect(obj).toHaveProperty("key");
expect(obj).toHaveProperty("key", expectedValue);
// Exceptions
expect(() => throwError()).toThrow();
expect(() => throwError()).toThrow(CustomError);
expect(() => throwError()).toThrow(/error message/);typescript
import { expect } from "bun:test";
// 相等性
expect(value).toBe(5); // 严格相等 (===)
expect(obj).toEqual({ a: 1 }); // 深度相等
expect(value).toStrictEqual(5); // 严格深度相等
// 真值判断
expect(value).toBeTruthy(); // 真值
expect(value).toBeFalsy(); // 假值
expect(value).toBeNull(); // null
expect(value).toBeUndefined(); // undefined
expect(value).toBeDefined(); // 非undefined
// 数字断言
expect(number).toBeGreaterThan(5);
expect(number).toBeGreaterThanOrEqual(5);
expect(number).toBeLessThan(10);
expect(number).toBeLessThanOrEqual(10);
expect(0.1 + 0.2).toBeCloseTo(0.3); // 浮点数比较
// 字符串断言
expect(string).toMatch(/pattern/);
expect(string).toContain("子字符串");
// 数组断言
expect(array).toContain(value);
expect(array).toHaveLength(3);
// 对象断言
expect(obj).toHaveProperty("key");
expect(obj).toHaveProperty("key", expectedValue);
// 异常断言
expect(() => throwError()).toThrow();
expect(() => throwError()).toThrow(CustomError);
expect(() => throwError()).toThrow(/错误信息/);Skipping and Only
跳过与仅运行指定测试
typescript
import { it, describe } from "bun:test";
describe("Feature", () => {
it("should test this", () => {
// Runs
});
it.skip("should skip this test", () => {
// Skipped
});
it.only("should run only this test", () => {
// Only this runs in the suite
});
describe.skip("skipped suite", () => {
it("won't run", () => {});
});
});typescript
import { it, describe } from "bun:test";
describe("功能模块", () => {
it("应该执行这个测试", () => {
// 正常运行
});
it.skip("应该跳过这个测试", () => {
// 被跳过
});
it.only("应该只运行这个测试", () => {
// 在测试套件中仅运行此测试
});
describe.skip("被跳过的测试套件", () => {
it("不会运行", () => {});
});
});Test.todo
Test.todo
typescript
import { it } from "bun:test";
it.todo("feature not yet implemented");
it.todo("edge case to handle");typescript
import { it } from "bun:test";
it.todo("尚未实现的功能");
it.todo("需要处理的边界情况");Setup and Teardown
测试前置与清理
beforeEach and afterEach
beforeEach与afterEach
typescript
import { describe, it, beforeEach, afterEach, expect } from "bun:test";
import { Database } from "./database";
describe("Database operations", () => {
let db: Database;
beforeEach(() => {
// Setup before each test
db = new Database(":memory:");
db.initialize();
});
afterEach(() => {
// Cleanup after each test
db.close();
});
it("should insert and retrieve data", () => {
db.insert("users", { id: 1, name: "John" });
const user = db.query("SELECT * FROM users WHERE id = 1");
expect(user.name).toBe("John");
});
});typescript
import { describe, it, beforeEach, afterEach, expect } from "bun:test";
import { Database } from "./database";
describe("数据库操作", () => {
let db: Database;
beforeEach(() => {
// 每个测试前的前置操作
db = new Database(":memory:");
db.initialize();
});
afterEach(() => {
// 每个测试后的清理操作
db.close();
});
it("应该能插入并检索数据", () => {
db.insert("users", { id: 1, name: "John" });
const user = db.query("SELECT * FROM users WHERE id = 1");
expect(user.name).toBe("John");
});
});beforeAll and afterAll
beforeAll与afterAll
typescript
import { describe, it, beforeAll, afterAll, expect } from "bun:test";
import { setupExpensiveResource } from "./resources";
describe("Resource-intensive operations", () => {
let resource: any;
beforeAll(() => {
// Setup once for entire suite
resource = setupExpensiveResource();
});
afterAll(() => {
// Cleanup once after entire suite
resource.teardown();
});
it("uses expensive resource", () => {
expect(resource.isReady()).toBe(true);
});
it("performs operation", () => {
const result = resource.process("data");
expect(result).toBeDefined();
});
});typescript
import { describe, it, beforeAll, afterAll, expect } from "bun:test";
import { setupExpensiveResource } from "./resources";
describe("资源密集型操作", () => {
let resource: any;
beforeAll(() => {
// 整个测试套件仅执行一次前置操作
resource = setupExpensiveResource();
});
afterAll(() => {
// 整个测试套件完成后仅执行一次清理
resource.teardown();
});
it("使用昂贵资源", () => {
expect(resource.isReady()).toBe(true);
});
it("执行操作", () => {
const result = resource.process("data");
expect(result).toBeDefined();
});
});Nested Hooks
嵌套钩子函数
typescript
import { describe, it, beforeEach } from "bun:test";
describe("Outer suite", () => {
let value = 0;
beforeEach(() => {
value = 10;
});
it("test in outer", () => {
expect(value).toBe(10);
});
describe("Inner suite", () => {
beforeEach(() => {
value *= 2; // Runs after outer beforeEach
});
it("test in inner", () => {
expect(value).toBe(20); // 10 * 2
});
});
});typescript
import { describe, it, beforeEach } from "bun:test";
describe("外层测试套件", () => {
let value = 0;
beforeEach(() => {
value = 10;
});
it("外层测试", () => {
expect(value).toBe(10);
});
describe("内层测试套件", () => {
beforeEach(() => {
value *= 2; // 在外层beforeEach之后运行
});
it("内层测试", () => {
expect(value).toBe(20); // 10 * 2
});
});
});Mocking with Bun
使用Bun进行Mock
Using mock()
使用mock()
typescript
import { mock } from "bun:test";
import { fetchUser } from "./api";
const mockFetch = mock((userId: string) => {
return { id: userId, name: "Mock User" };
});
// Test mock behavior
const result = mockFetch("123");
expect(result.name).toBe("Mock User");
expect(mockFetch.mock.calls.length).toBe(1);
expect(mockFetch.mock.calls[0]).toEqual(["123"]);typescript
import { mock } from "bun:test";
import { fetchUser } from "./api";
const mockFetch = mock((userId: string) => {
return { id: userId, name: "模拟用户" };
});
// 测试mock行为
const result = mockFetch("123");
expect(result.name).toBe("模拟用户");
expect(mockFetch.mock.calls.length).toBe(1);
expect(mockFetch.mock.calls[0]).toEqual(["123"]);Mock Objects and Modules
Mock对象与模块
typescript
import { describe, it, expect, mock } from "bun:test";
describe("Service with mocked dependency", () => {
it("should use mocked database", () => {
const mockDb = {
query: mock((sql: string) => [{ id: 1, name: "Test" }]),
close: mock(() => {}),
};
const service = new Service(mockDb);
const result = service.getUser(1);
expect(result.name).toBe("Test");
expect(mockDb.query.mock.calls.length).toBe(1);
});
});typescript
import { describe, it, expect, mock } from "bun:test";
describe("依赖被Mock的服务", () => {
it("应该使用Mock数据库", () => {
const mockDb = {
query: mock((sql: string) => [{ id: 1, name: "测试用户" }]),
close: mock(() => {}),
};
const service = new Service(mockDb);
const result = service.getUser(1);
expect(result.name).toBe("测试用户");
expect(mockDb.query.mock.calls.length).toBe(1);
});
});Module Mocking
模块Mock
typescript
import { describe, it, expect, mock } from "bun:test";
import { getUserFromAPI } from "./api";
// Mock entire modules
mock.module("./api", () => ({
getUserFromAPI: mock((id: string) => ({
id,
name: "Mocked User",
})),
}));
describe("API integration", () => {
it("should work with mocked API", async () => {
const user = await getUserFromAPI("123");
expect(user.name).toBe("Mocked User");
});
});typescript
import { describe, it, expect, mock } from "bun:test";
import { getUserFromAPI } from "./api";
// Mock整个模块
mock.module("./api", () => ({
getUserFromAPI: mock((id: string) => ({
id,
name: "Mocked User",
})),
}));
describe("API集成测试", () => {
it("应该与Mock API正常协作", async () => {
const user = await getUserFromAPI("123");
expect(user.name).toBe("Mocked User");
});
});Spy on Function Calls
函数调用Spy
typescript
import { describe, it, expect, mock } from "bun:test";
describe("Spy on calls", () => {
it("should track function calls", () => {
const originalFunc = (x: number) => x * 2;
const spied = mock(originalFunc);
const result1 = spied(5);
const result2 = spied(10);
expect(result1).toBe(10);
expect(result2).toBe(20);
expect(spied.mock.calls.length).toBe(2);
expect(spied.mock.results[0].value).toBe(10);
expect(spied.mock.results[1].value).toBe(20);
});
});typescript
import { describe, it, expect, mock } from "bun:test";
describe("Spy函数调用", () => {
it("应该追踪函数调用", () => {
const originalFunc = (x: number) => x * 2;
const spied = mock(originalFunc);
const result1 = spied(5);
const result2 = spied(10);
expect(result1).toBe(10);
expect(result2).toBe(20);
expect(spied.mock.calls.length).toBe(2);
expect(spied.mock.results[0].value).toBe(10);
expect(spied.mock.results[1].value).toBe(20);
});
});Mock Return Values
Mock返回值
typescript
import { describe, it, expect, mock } from "bun:test";
describe("Mock return values", () => {
it("should return configured values", () => {
const mockFunc = mock();
// Set return values for specific calls
mockFunc.mock.returns = [
{ value: "first" },
{ value: "second" },
{ value: "third" },
];
expect(mockFunc()).toEqual({ value: "first" });
expect(mockFunc()).toEqual({ value: "second" });
});
it("should throw errors when configured", () => {
const errorMock = mock(() => {
throw new Error("Mocked error");
});
expect(() => errorMock()).toThrow("Mocked error");
});
});typescript
import { describe, it, expect, mock } from "bun:test";
describe("Mock返回值", () => {
it("应该返回配置好的值", () => {
const mockFunc = mock();
// 为特定调用设置返回值
mockFunc.mock.returns = [
{ value: "第一次返回" },
{ value: "第二次返回" },
{ value: "第三次返回" },
];
expect(mockFunc()).toEqual({ value: "第一次返回" });
expect(mockFunc()).toEqual({ value: "第二次返回" });
});
it("在配置后应该抛出错误", () => {
const errorMock = mock(() => {
throw new Error("Mocked error");
});
expect(() => errorMock()).toThrow("Mocked error");
});
});Async Testing
异步测试
Async/Await in Tests
测试中的Async/Await
typescript
import { describe, it, expect } from "bun:test";
import { fetchUser } from "./api";
describe("Async operations", () => {
it("should fetch user data", async () => {
const user = await fetchUser("123");
expect(user.id).toBe("123");
expect(user.name).toBeDefined();
});
it("should handle fetch errors", async () => {
expect(fetchUser("invalid")).rejects.toThrow();
});
});typescript
import { describe, it, expect } from "bun:test";
import { fetchUser } from "./api";
describe("异步操作", () => {
it("应该能获取用户数据", async () => {
const user = await fetchUser("123");
expect(user.id).toBe("123");
expect(user.name).toBeDefined();
});
it("应该能处理获取错误", async () => {
expect(fetchUser("invalid")).rejects.toThrow();
});
});Promise Testing
Promise测试
typescript
import { describe, it, expect } from "bun:test";
describe("Promise handling", () => {
it("should resolve with data", () => {
const promise = Promise.resolve({ id: 1, name: "User" });
return expect(promise).resolves.toEqual({ id: 1, name: "User" });
});
it("should reject with error", () => {
const promise = Promise.reject(new Error("Failed"));
return expect(promise).rejects.toThrow("Failed");
});
});typescript
import { describe, it, expect } from "bun:test";
describe("Promise处理", () => {
it("应该能解析并返回数据", () => {
const promise = Promise.resolve({ id: 1, name: "用户" });
return expect(promise).resolves.toEqual({ id: 1, name: "用户" });
});
it("应该能拒绝并返回错误", () => {
const promise = Promise.reject(new Error("失败"));
return expect(promise).rejects.toThrow("失败");
});
});Concurrent Async Tests
并发异步测试
typescript
import { describe, it, expect } from "bun:test";
describe("Concurrent operations", () => {
it("should handle multiple concurrent requests", async () => {
const results = await Promise.all([
fetchData("1"),
fetchData("2"),
fetchData("3"),
]);
expect(results).toHaveLength(3);
expect(results[0].id).toBe("1");
expect(results[1].id).toBe("2");
expect(results[2].id).toBe("3");
});
it("should race multiple promises", async () => {
const winner = await Promise.race([
slowOperation(100),
slowOperation(50),
slowOperation(200),
]);
expect(winner).toBeDefined();
});
});typescript
import { describe, it, expect } from "bun:test";
describe("并发操作", () => {
it("应该能处理多个并发请求", async () => {
const results = await Promise.all([
fetchData("1"),
fetchData("2"),
fetchData("3"),
]);
expect(results).toHaveLength(3);
expect(results[0].id).toBe("1");
expect(results[1].id).toBe("2");
expect(results[2].id).toBe("3");
});
it("应该能在多个Promise中竞速", async () => {
const winner = await Promise.race([
slowOperation(100),
slowOperation(50),
slowOperation(200),
]);
expect(winner).toBeDefined();
});
});Test Fixtures and Utilities
测试夹具与工具
Shared Test Data
共享测试数据
typescript
// tests/fixtures/users.ts
export const testUsers = {
admin: {
id: "1",
email: "admin@example.com",
role: "admin",
},
user: {
id: "2",
email: "user@example.com",
role: "user",
},
guest: {
id: "3",
email: "guest@example.com",
role: "guest",
},
};
export const invalidUsers = {
noEmail: { id: "4" },
invalidEmail: { id: "5", email: "not-an-email" },
noId: { email: "test@example.com" },
};
// In test file
import { describe, it, expect } from "bun:test";
import { testUsers } from "./fixtures/users";
describe("User roles", () => {
it("should verify admin role", () => {
expect(testUsers.admin.role).toBe("admin");
});
});typescript
// tests/fixtures/users.ts
export const testUsers = {
admin: {
id: "1",
email: "admin@example.com",
role: "admin",
},
user: {
id: "2",
email: "user@example.com",
role: "user",
},
guest: {
id: "3",
email: "guest@example.com",
role: "guest",
},
};
export const invalidUsers = {
noEmail: { id: "4" },
invalidEmail: { id: "5", email: "not-an-email" },
noId: { email: "test@example.com" },
};
// 在测试文件中
import { describe, it, expect } from "bun:test";
import { testUsers } from "./fixtures/users";
describe("用户角色", () => {
it("应该验证管理员角色", () => {
expect(testUsers.admin.role).toBe("admin");
});
});Fixture Setup Function
夹具设置函数
typescript
// tests/fixtures/setup.ts
export function createMockUser(overrides: Partial<User> = {}): User {
return {
id: "test-id",
email: "test@example.com",
name: "Test User",
role: "user",
createdAt: new Date(),
...overrides,
};
}
export function createMockDatabase() {
const users: User[] = [];
return {
addUser: (user: User) => {
users.push(user);
return user;
},
getUser: (id: string) => users.find(u => u.id === id),
getAllUsers: () => [...users],
clear: () => users.splice(0),
};
}
// In test
import { describe, it, beforeEach, expect } from "bun:test";
import { createMockUser, createMockDatabase } from "./fixtures/setup";
describe("User repository", () => {
let db: ReturnType<typeof createMockDatabase>;
beforeEach(() => {
db = createMockDatabase();
});
it("should add and retrieve users", () => {
const user = createMockUser({ name: "John Doe" });
db.addUser(user);
expect(db.getUser(user.id)?.name).toBe("John Doe");
});
});typescript
// tests/fixtures/setup.ts
export function createMockUser(overrides: Partial<User> = {}): User {
return {
id: "test-id",
email: "test@example.com",
name: "测试用户",
role: "user",
createdAt: new Date(),
...overrides,
};
}
export function createMockDatabase() {
const users: User[] = [];
return {
addUser: (user: User) => {
users.push(user);
return user;
},
getUser: (id: string) => users.find(u => u.id === id),
getAllUsers: () => [...users],
clear: () => users.splice(0),
};
}
// 在测试中
import { describe, it, beforeEach, expect } from "bun:test";
import { createMockUser, createMockDatabase } from "./fixtures/setup";
describe("用户仓库", () => {
let db: ReturnType<typeof createMockDatabase>;
beforeEach(() => {
db = createMockDatabase();
});
it("应该能添加并检索用户", () => {
const user = createMockUser({ name: "John Doe" });
db.addUser(user);
expect(db.getUser(user.id)?.name).toBe("John Doe");
});
});Coverage with Bun
使用Bun生成覆盖率报告
Running Coverage
运行覆盖率测试
bash
undefinedbash
undefinedGenerate coverage report
生成覆盖率报告
bun test --coverage
bun test --coverage
Coverage with specific files
针对特定文件生成覆盖率
bun test --coverage src/
bun test --coverage src/
HTML coverage report
生成HTML格式覆盖率报告
bun test --coverage --coverage-html
undefinedbun test --coverage --coverage-html
undefinedConfiguration in bunfig.toml
在bunfig.toml中配置
toml
[test]toml
[test]Enable coverage
启用覆盖率
coverage = true
coverage = true
Coverage reporting format
覆盖率报告格式
coverageFormat = ["text", "html", "json"]
coverageFormat = ["text", "html", "json"]
Files to report on
覆盖率阈值
coverageThreshold = 80
coverageThreshold = 80
Exclude from coverage
排除覆盖率统计的文件
coverageIgnore = ["/node_modules/", "/dist/"]
coverageIgnore = ["/node_modules/", "/dist/"]
Root directory for coverage
覆盖率统计的根目录
coverageRoot = "src"
undefinedcoverageRoot = "src"
undefinedCoverage Reports
覆盖率报告输出
bash
undefinedbash
undefinedText report
文本格式报告
bun test --coverage
bun test --coverage
Generate HTML report in coverage/
在coverage/目录生成HTML报告
bun test --coverage --coverage-html
bun test --coverage --coverage-html
JSON report for CI/CD
生成JSON格式报告用于CI/CD
bun test --coverage coverage/coverage.json
---bun test --coverage coverage/coverage.json
---Integration Testing
集成测试
Testing HTTP APIs
HTTP API测试
typescript
import { describe, it, expect, beforeAll, afterAll } from "bun:test";
import { startServer, stopServer } from "./server";
describe("API Integration", () => {
let baseUrl: string;
beforeAll(async () => {
const server = await startServer();
baseUrl = `http://localhost:${server.port}`;
});
afterAll(async () => {
await stopServer();
});
it("should create a user", async () => {
const response = await fetch(`${baseUrl}/api/users`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: "test@example.com" }),
});
expect(response.status).toBe(201);
const data = await response.json();
expect(data.id).toBeDefined();
});
it("should retrieve user", async () => {
const response = await fetch(`${baseUrl}/api/users/1`);
expect(response.status).toBe(200);
});
});typescript
import { describe, it, expect, beforeAll, afterAll } from "bun:test";
import { startServer, stopServer } from "./server";
describe("API集成测试", () => {
let baseUrl: string;
beforeAll(async () => {
const server = await startServer();
baseUrl = `http://localhost:${server.port}`;
});
afterAll(async () => {
await stopServer();
});
it("应该能创建用户", async () => {
const response = await fetch(`${baseUrl}/api/users`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: "test@example.com" }),
});
expect(response.status).toBe(201);
const data = await response.json();
expect(data.id).toBeDefined();
});
it("应该能检索用户", async () => {
const response = await fetch(`${baseUrl}/api/users/1`);
expect(response.status).toBe(200);
});
});Database Integration
数据库集成测试
typescript
import { describe, it, expect, beforeAll, afterAll } from "bun:test";
import { Database } from "./database";
describe("Database operations", () => {
let db: Database;
beforeAll(async () => {
db = new Database(":memory:");
await db.initialize();
await db.runMigrations();
});
afterAll(async () => {
await db.close();
});
it("should perform CRUD operations", async () => {
// Create
const user = await db.users.create({
email: "test@example.com",
name: "Test User",
});
expect(user.id).toBeDefined();
// Read
const retrieved = await db.users.findById(user.id);
expect(retrieved.email).toBe("test@example.com");
// Update
await db.users.update(user.id, { name: "Updated" });
const updated = await db.users.findById(user.id);
expect(updated.name).toBe("Updated");
// Delete
await db.users.delete(user.id);
const deleted = await db.users.findById(user.id);
expect(deleted).toBeNull();
});
});typescript
import { describe, it, expect, beforeAll, afterAll } from "bun:test";
import { Database } from "./database";
describe("数据库操作", () => {
let db: Database;
beforeAll(async () => {
db = new Database(":memory:");
await db.initialize();
await db.runMigrations();
});
afterAll(async () => {
await db.close();
});
it("应该能执行CRUD操作", async () => {
// 创建
const user = await db.users.create({
email: "test@example.com",
name: "测试用户",
});
expect(user.id).toBeDefined();
// 读取
const retrieved = await db.users.findById(user.id);
expect(retrieved.email).toBe("test@example.com");
// 更新
await db.users.update(user.id, { name: "已更新" });
const updated = await db.users.findById(user.id);
expect(updated.name).toBe("已更新");
// 删除
await db.users.delete(user.id);
const deleted = await db.users.findById(user.id);
expect(deleted).toBeNull();
});
});Testing TypeScript Types
TypeScript类型测试
Type Testing with TypeScript
使用TypeScript进行类型测试
typescript
import { describe, it, expectTypeOf } from "bun:test";
import { processUser } from "./user-processor";
describe("Type safety", () => {
it("should have correct return type", () => {
const result = processUser({ name: "John", age: 30 });
// Check type at compile time
expectTypeOf(result).toMatchTypeOf<{ success: boolean }>();
});
it("should enforce parameter types", () => {
// TypeScript will catch these at compile time
// @ts-expect-error - wrong type
processUser({ name: 123 });
// @ts-expect-error - missing required field
processUser({ age: 30 });
});
});typescript
import { describe, it, expectTypeOf } from "bun:test";
import { processUser } from "./user-processor";
describe("类型安全", () => {
it("应该有正确的返回类型", () => {
const result = processUser({ name: "John", age: 30 });
// 在编译时检查类型
expectTypeOf(result).toMatchTypeOf<{ success: boolean }>();
});
it("应该强制参数类型", () => {
// TypeScript会在编译时捕获这些错误
// @ts-expect-error - 错误类型
processUser({ name: 123 });
// @ts-expect-error - 缺少必填字段
processUser({ age: 30 });
});
});Configuration in bunfig.toml
bunfig.toml中的配置
Complete Test Configuration
完整测试配置
toml
[test]toml
[test]Test file patterns
测试文件匹配规则
root = "."
prefix = ""
suffix = [".test", ".spec"]
testNamePattern = ""
root = "."
prefix = ""
suffix = [".test", ".spec"]
testNamePattern = ""
Coverage
覆盖率配置
coverage = true
coverageFormat = ["text", "html", "json"]
coverageThreshold = 80
coverageRoot = "src"
coverageIgnore = ["/node_modules/"]
coverage = true
coverageFormat = ["text", "html", "json"]
coverageThreshold = 80
coverageRoot = "src"
coverageIgnore = ["/node_modules/"]
Test execution
测试执行配置
bail = false
timeout = 30000
reportFailures = true
bail = false
timeout = 30000
reportFailures = true
Reporters
报告器
reporters = ["spec"] # or ["tap", "junit"]
reporters = ["spec"] # 或 ["tap", "junit"]
Output
输出配置
preloadModules = []
undefinedpreloadModules = []
undefinedWith npm scripts in package.json
在package.json中配置npm脚本
json
{
"scripts": {
"test": "bun test",
"test:watch": "bun test --watch",
"test:coverage": "bun test --coverage",
"test:ui": "bun test --coverage --coverage-html",
"test:single": "bun test tests/unit.test.ts",
"test:bail": "bun test --bail",
"test:debug": "bun test --inspect-brk"
}
}json
{
"scripts": {
"test": "bun test",
"test:watch": "bun test --watch",
"test:coverage": "bun test --coverage",
"test:ui": "bun test --coverage --coverage-html",
"test:single": "bun test tests/unit.test.ts",
"test:bail": "bun test --bail",
"test:debug": "bun test --inspect-brk"
}
}React Component Testing
React组件测试
Testing React Components
测试React组件
typescript
import { describe, it, expect } from "bun:test";
import { render, screen } from "bun:test:dom";
import { Button } from "./Button";
describe("Button component", () => {
it("should render button with text", () => {
render(<Button label="Click me" />);
const button = screen.getByRole("button", { name: "Click me" });
expect(button).toBeDefined();
});
it("should call onClick handler", async () => {
const handleClick = mock();
render(<Button label="Click" onClick={handleClick} />);
const button = screen.getByRole("button");
button.click();
expect(handleClick.mock.calls.length).toBe(1);
});
it("should disable button when disabled prop is true", () => {
render(<Button label="Disabled" disabled={true} />);
const button = screen.getByRole("button") as HTMLButtonElement;
expect(button.disabled).toBe(true);
});
});typescript
import { describe, it, expect } from "bun:test";
import { render, screen } from "bun:test:dom";
import { Button } from "./Button";
describe("Button组件", () => {
it("应该渲染带文本的按钮", () => {
render(<Button label="点击我" />);
const button = screen.getByRole("button", { name: "点击我" });
expect(button).toBeDefined();
});
it("应该调用onClick处理函数", async () => {
const handleClick = mock();
render(<Button label="点击" onClick={handleClick} />);
const button = screen.getByRole("button");
button.click();
expect(handleClick.mock.calls.length).toBe(1);
});
it("当disabled属性为true时应该禁用按钮", () => {
render(<Button label="已禁用" disabled={true} />);
const button = screen.getByRole("button") as HTMLButtonElement;
expect(button.disabled).toBe(true);
});
});Common Testing Patterns
常用测试模式
Arrange-Act-Assert
准备-执行-断言模式
typescript
import { describe, it, expect } from "bun:test";
import { calculateTotal } from "./calculator";
describe("calculateTotal", () => {
it("should sum array of numbers", () => {
// Arrange - Set up test data
const items = [
{ price: 10, quantity: 2 },
{ price: 5, quantity: 3 },
];
// Act - Execute functionality
const total = calculateTotal(items);
// Assert - Verify results
expect(total).toBe(35); // (10*2) + (5*3)
});
});typescript
import { describe, it, expect } from "bun:test";
import { calculateTotal } from "./calculator";
describe("calculateTotal函数", () => {
it("应该对数字数组求和", () => {
// 准备 - 设置测试数据
const items = [
{ price: 10, quantity: 2 },
{ price: 5, quantity: 3 },
];
// 执行 - 调用功能函数
const total = calculateTotal(items);
// 断言 - 验证结果
expect(total).toBe(35); // (10*2) + (5*3)
});
});Testing Error Conditions
错误条件测试
typescript
import { describe, it, expect } from "bun:test";
import { validateEmail } from "./validators";
describe("validateEmail", () => {
it("should validate correct email", () => {
expect(validateEmail("test@example.com")).toBe(true);
});
it("should reject invalid emails", () => {
expect(validateEmail("not-an-email")).toBe(false);
expect(validateEmail("@example.com")).toBe(false);
expect(validateEmail("test@")).toBe(false);
});
it("should throw on null input", () => {
expect(() => validateEmail(null as any)).toThrow();
});
});typescript
import { describe, it, expect } from "bun:test";
import { validateEmail } from "./validators";
describe("validateEmail函数", () => {
it("应该验证正确的邮箱", () => {
expect(validateEmail("test@example.com")).toBe(true);
});
it("应该拒绝无效邮箱", () => {
expect(validateEmail("not-an-email")).toBe(false);
expect(validateEmail("@example.com")).toBe(false);
expect(validateEmail("test@")).toBe(false);
});
it("当输入为null时应该抛出错误", () => {
expect(() => validateEmail(null as any)).toThrow();
});
});Testing Class Methods
类方法测试
typescript
import { describe, it, expect, beforeEach } from "bun:test";
import { Counter } from "./counter";
describe("Counter class", () => {
let counter: Counter;
beforeEach(() => {
counter = new Counter();
});
it("should increment", () => {
counter.increment();
expect(counter.value).toBe(1);
});
it("should decrement", () => {
counter.increment();
counter.decrement();
expect(counter.value).toBe(0);
});
it("should reset to zero", () => {
counter.increment();
counter.increment();
counter.reset();
expect(counter.value).toBe(0);
});
});typescript
import { describe, it, expect, beforeEach } from "bun:test";
import { Counter } from "./counter";
describe("Counter类", () => {
let counter: Counter;
beforeEach(() => {
counter = new Counter();
});
it("应该能自增", () => {
counter.increment();
expect(counter.value).toBe(1);
});
it("应该能自减", () => {
counter.increment();
counter.decrement();
expect(counter.value).toBe(0);
});
it("应该能重置为零", () => {
counter.increment();
counter.increment();
counter.reset();
expect(counter.value).toBe(0);
});
});Edge Case Testing
边界情况测试
Common Edge Cases for TypeScript
TypeScript常用边界情况
typescript
import { describe, it, expect } from "bun:test";
import { processArray } from "./processor";
describe("processArray edge cases", () => {
it("should handle empty array", () => {
expect(processArray([])).toEqual([]);
});
it("should handle single item", () => {
expect(processArray([1])).toEqual([1]);
});
it("should handle undefined values", () => {
const result = processArray([1, undefined, 3]);
expect(result).toContain(1);
expect(result).toContain(3);
});
it("should handle null values", () => {
const result = processArray([1, null, 3]);
expect(result.length).toBeLessThanOrEqual(3);
});
it("should handle very large numbers", () => {
const large = Number.MAX_SAFE_INTEGER;
expect(processArray([large, large])).toBeDefined();
});
it("should handle special values", () => {
expect(processArray([0, -0, NaN])).toBeDefined();
});
});typescript
import { describe, it, expect } from "bun:test";
import { processArray } from "./processor";
describe("processArray函数边界情况", () => {
it("应该能处理空数组", () => {
expect(processArray([])).toEqual([]);
});
it("应该能处理单个元素", () => {
expect(processArray([1])).toEqual([1]);
});
it("应该能处理undefined值", () => {
const result = processArray([1, undefined, 3]);
expect(result).toContain(1);
expect(result).toContain(3);
});
it("应该能处理null值", () => {
const result = processArray([1, null, 3]);
expect(result.length).toBeLessThanOrEqual(3);
});
it("应该能处理极大数字", () => {
const large = Number.MAX_SAFE_INTEGER;
expect(processArray([large, large])).toBeDefined();
});
it("应该能处理特殊值", () => {
expect(processArray([0, -0, NaN])).toBeDefined();
});
});Null/Undefined Handling
Null/Undefined处理
typescript
import { describe, it, expect } from "bun:test";
import { getUser } from "./user-service";
describe("Null/undefined handling", () => {
it("should return null for missing user", async () => {
const user = await getUser("nonexistent");
expect(user).toBeNull();
});
it("should handle undefined optional fields", async () => {
const user = await getUser("123");
if (user) {
expect(user.middleName).toBeUndefined();
}
});
it("should distinguish null from undefined", () => {
const nullValue = null;
const undefinedValue = undefined;
expect(nullValue).toBeNull();
expect(undefinedValue).toBeUndefined();
expect(nullValue).not.toBe(undefinedValue);
});
});typescript
import { describe, it, expect } from "bun:test";
import { getUser } from "./user-service";
describe("Null/undefined处理", () => {
it("对于不存在的用户应该返回null", async () => {
const user = await getUser("nonexistent");
expect(user).toBeNull();
});
it("应该能处理undefined可选字段", async () => {
const user = await getUser("123");
if (user) {
expect(user.middleName).toBeUndefined();
}
});
it("应该区分null和undefined", () => {
const nullValue = null;
const undefinedValue = undefined;
expect(nullValue).toBeNull();
expect(undefinedValue).toBeUndefined();
expect(nullValue).not.toBe(undefinedValue);
});
});Zero-Warnings Policy
零警告策略
Treat Warnings as Errors
将警告视为错误
Running tests should produce zero warnings:
bash
undefined运行测试时应该产生零警告:
bash
undefinedRun tests and fail on any warnings
运行测试并在出现任何警告时失败
bun test
bun test
If warnings appear, identify and fix them
如果出现警告,定位并修复它们
Common causes:
常见原因:
- Deprecated API usage
- 使用已废弃的API
- Unhandled promise rejections
- 未处理的Promise拒绝
- Memory leaks in tests
- 测试中的内存泄漏
- Resource cleanup issues
- 资源清理问题
undefinedundefinedConfiguration for Warnings
警告相关配置
toml
[test]toml
[test]Fail on warnings (if available in your Bun version)
出现警告时失败(如果你的Bun版本支持)
reportFailures = true
reportFailures = true
Configure reporters to show warnings
配置报告器以显示警告
reporters = ["spec"]
undefinedreporters = ["spec"]
undefinedHandling Expected Warnings
处理预期警告
If a library produces unavoidable warnings:
typescript
import { describe, it, expect } from "bun:test";
describe("Feature with expected warning", () => {
it("should work despite library warning", () => {
// This test runs code that produces a library warning
// Document why the warning is acceptable
expect(unsafeLibraryFunction()).toBeDefined();
});
});如果某个库产生不可避免的警告:
typescript
import { describe, it, expect } from "bun:test";
describe("存在预期警告的功能", () => {
it("即使有库警告也应该正常工作", () => {
// 此测试运行的代码会产生库警告
// 记录为什么该警告是可接受的
expect(unsafeLibraryFunction()).toBeDefined();
});
});Makefile Integration
Makefile集成
Test Targets
测试目标
makefile
.PHONY: test test-watch test-coverage test-single test-bailmakefile
.PHONY: test test-watch test-coverage test-single test-bailRun all tests
运行所有测试
test:
bun test
test:
bun test
Watch mode - rerun on file changes
监听模式 - 文件变化时重新运行测试
test-watch:
bun test --watch
test-watch:
bun test --watch
Run with coverage
运行测试并生成覆盖率报告
test-coverage:
bun test --coverage
test-coverage:
bun test --coverage
View HTML coverage report
查看HTML格式覆盖率报告
test-ui:
bun test --coverage --coverage-html
@echo "Coverage report: coverage/index.html"
test-ui:
bun test --coverage --coverage-html
@echo "覆盖率报告地址:coverage/index.html"
Run single test file
运行单个测试文件
test-single:
bun test tests/specific.test.ts
test-single:
bun test tests/specific.test.ts
Fail on first error
遇到第一个错误时立即失败
test-bail:
bun test --bail
test-bail:
bun test --bail
Debug tests
调试测试
test-debug:
bun test --inspect-brk
test-debug:
bun test --inspect-brk
Full test suite with checks
完整测试套件检查
check: test lint type-check
@echo "All checks passed!"
---check: test lint type-check
@echo "所有检查通过!"
---Project Structure Patterns
项目结构模式
Organized Test Structure
有序的测试结构
src/
├── utils/
│ ├── math.ts
│ ├── math.test.ts # Colocated with source
│ └── string.ts
├── services/
│ ├── api.ts
│ └── api.test.ts
├── __tests__/ # Alternative: centralized tests
│ ├── fixtures/
│ │ ├── users.ts
│ │ └── setup.ts
│ ├── unit/
│ │ └── math.test.ts
│ ├── integration/
│ │ └── api.test.ts
│ └── e2e/
│ └── workflow.test.ts
└── index.tssrc/
├── utils/
│ ├── math.ts
│ ├── math.test.ts # 与源代码同目录
│ └── string.ts
├── services/
│ ├── api.ts
│ └── api.test.ts
├── __tests__/ # 替代方案:集中式测试目录
│ ├── fixtures/
│ │ ├── users.ts
│ │ └── setup.ts
│ ├── unit/
│ │ └── math.test.ts
│ ├── integration/
│ │ └── api.test.ts
│ └── e2e/
│ └── workflow.test.ts
└── index.tsTest Fixtures Directory
测试夹具目录
tests/
├── fixtures/
│ ├── users.ts # Test user data
│ ├── database.ts # Test database setup
│ ├── api-responses.ts # Mock API responses
│ └── setup.ts # Fixture functions
└── helpers/
├── assertions.ts # Custom assertions
└── mocks.ts # Mock utilitiestests/
├── fixtures/
│ ├── users.ts # 测试用户数据
│ ├── database.ts # 测试数据库设置
│ ├── api-responses.ts # Mock API响应
│ └── setup.ts # 夹具函数
└── helpers/
├── assertions.ts # 自定义断言
└── mocks.ts # Mock工具Dependency Installation
依赖安装
Adding Test Dependencies
添加测试依赖
bash
undefinedbash
undefinedCore testing (already built-in with Bun)
核心测试功能(Bun已内置)
No installation needed - use "bun:test"
无需安装 - 直接使用"bun:test"
Additional testing utilities (optional)
额外测试工具(可选)
bun add --dev @types/bun
bun add --dev @types/bun
React testing (if using React)
React测试(如果使用React)
bun add --dev jsdom
bun add --dev jsdom
HTTP testing utilities
HTTP测试工具
bun add --dev node-fetch @types/node
bun add --dev node-fetch @types/node
Test data generation
测试数据生成
bun add --dev faker
---
**Note:** For general testing principles and strategies not specific to TypeScript/JavaScript, see the testing-workflow skill.bun add --dev faker
---
**注意:** 对于不特定于TypeScript/JavaScript的通用测试原则和策略,请参考testing-workflow技能文档。