名称: bun-testing 用户可调用: false 描述: 在使用Bun内置测试运行器编写测试时使用。涵盖测试组织、断言、模拟和使用Bun快速测试基础设施的快照测试。 允许的工具:
- 读取
- 写入
- 编辑
- Bash
- Grep
- Glob
Bun测试
使用此技能当使用Bun内置的测试运行器编写测试时,该运行器提供与Jest兼容的API,并具有显著更快的执行速度。
关键概念
测试运行器基础
Bun包含一个开箱即用的内置测试运行器:
import { test, expect, describe, beforeAll, afterAll } from "bun:test";
describe("数学运算", () => {
test("加法", () => {
expect(1 + 1).toBe(2);
});
test("减法", () => {
expect(5 - 3).toBe(2);
});
});
运行测试
# 运行所有测试
bun test
# 运行特定测试文件
bun test ./src/utils.test.ts
# 运行覆盖率测试
bun test --coverage
# 监视模式
bun test --watch
匹配器和断言
Bun支持与Jest兼容的匹配器:
import { test, expect } from "bun:test";
test("匹配器", () => {
// 相等性
expect(42).toBe(42);
expect({ a: 1 }).toEqual({ a: 1 });
// 真值性
expect(true).toBeTruthy();
expect(false).toBeFalsy();
expect(null).toBeNull();
expect(undefined).toBeUndefined();
// 数字
expect(10).toBeGreaterThan(5);
expect(3).toBeLessThan(5);
expect(3.14).toBeCloseTo(3.1, 1);
// 字符串
expect("hello world").toContain("hello");
expect("test@example.com").toMatch(/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/);
// 数组
expect([1, 2, 3]).toContain(2);
expect([1, 2, 3]).toHaveLength(3);
// 对象
expect({ a: 1, b: 2 }).toHaveProperty("a");
expect({ a: 1, b: 2 }).toMatchObject({ a: 1 });
// 错误
expect(() => {
throw new Error("测试错误");
}).toThrow("测试错误");
});
最佳实践
使用describe/test组织测试
以清晰的层次结构组织测试:
import { describe, test, expect } from "bun:test";
describe("用户服务", () => {
describe("创建用户", () => {
test("使用有效数据创建用户", () => {
// 测试实现
});
test("使用无效邮箱时抛出错误", () => {
// 测试实现
});
});
describe("查找用户", () => {
test("通过ID查找现有用户", () => {
// 测试实现
});
test("对于不存在的用户返回null", () => {
// 测试实现
});
});
});
使用设置和清理钩子
在测试之间清理状态:
import { describe, test, beforeAll, afterAll, beforeEach, afterEach } from "bun:test";
describe("数据库测试", () => {
beforeAll(() => {
// 在所有测试之前运行一次
console.log("设置测试数据库");
});
afterAll(() => {
// 在所有测试之后运行一次
console.log("清理测试数据库");
});
beforeEach(() => {
// 在每个测试之前运行
console.log("重置测试数据");
});
afterEach(() => {
// 在每个测试之后运行
console.log("清理测试数据");
});
test("示例测试", () => {
expect(true).toBe(true);
});
});
使用Bun进行模拟
使用Bun内置的模拟功能:
import { test, expect, mock } from "bun:test";
test("模拟函数", () => {
const mockFn = mock((x: number) => x * 2);
mockFn(2);
mockFn(3);
expect(mockFn).toHaveBeenCalledTimes(2);
expect(mockFn).toHaveBeenCalledWith(2);
expect(mockFn).toHaveBeenCalledWith(3);
expect(mockFn.mock.results[0].value).toBe(4);
});
test("模拟模块", async () => {
// 模拟模块
mock.module("./api", () => ({
fetchData: mock(() => Promise.resolve({ data: "模拟数据" })),
}));
const { fetchData } = await import("./api");
const result = await fetchData();
expect(result).toEqual({ data: "模拟数据" });
});
异步测试
正确处理异步代码:
import { test, expect } from "bun:test";
test("异步函数", async () => {
const data = await fetchData();
expect(data).toBeDefined();
});
test("Promise", () => {
return fetchData().then((data) => {
expect(data).toBeDefined();
});
});
test("async/await带错误", async () => {
await expect(async () => {
await fetchInvalidData();
}).toThrow("无效数据");
});
常见模式
测试HTTP端点
import { describe, test, expect } from "bun:test";
describe("API端点", () => {
test("GET /api/users返回用户列表", async () => {
const response = await fetch("http://localhost:3000/api/users");
const users = await response.json();
expect(response.status).toBe(200);
expect(Array.isArray(users)).toBe(true);
});
test("POST /api/users创建新用户", async () => {
const newUser = { name: "Alice", email: "alice@example.com" };
const response = await fetch("http://localhost:3000/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(newUser),
});
expect(response.status).toBe(201);
const user = await response.json();
expect(user).toMatchObject(newUser);
expect(user.id).toBeDefined();
});
});
测试文件操作
import { test, expect, beforeEach, afterEach } from "bun:test";
import { unlink } from "fs/promises";
describe("文件操作", () => {
const testFile = "./test-output.txt";
afterEach(async () => {
try {
await unlink(testFile);
} catch {}
});
test("成功写入文件", async () => {
await Bun.write(testFile, "测试内容");
const file = Bun.file(testFile);
expect(await file.exists()).toBe(true);
const content = await file.text();
expect(content).toBe("测试内容");
});
});
快照测试
import { test, expect } from "bun:test";
test("快照测试", () => {
const data = {
id: 1,
name: "Alice",
email: "alice@example.com",
};
expect(data).toMatchSnapshot();
});
参数化测试
import { test, expect } from "bun:test";
const testCases = [
{ input: 1, expected: 2 },
{ input: 2, expected: 4 },
{ input: 3, expected: 6 },
];
testCases.forEach(({ input, expected }) => {
test(`double(${input})应等于${expected}`, () => {
expect(double(input)).toBe(expected);
});
});
测试计时器
import { test, expect } from "bun:test";
test("延迟执行", async () => {
let executed = false;
setTimeout(() => {
executed = true;
}, 100);
await new Promise((resolve) => setTimeout(resolve, 150));
expect(executed).toBe(true);
});
反模式
不要使用外部测试运行器
// 错误 - 安装Jest或其他测试运行器
// package.json
{
"devDependencies": {
"jest": "^29.0.0"
}
}
// 正确 - 使用Bun内置测试运行器
bun test
不要忘记清理
// 错误 - 测试污染
test("测试1", () => {
globalState.value = 10;
expect(globalState.value).toBe(10);
});
test("测试2", () => {
// 可能因测试1的状态而失败
expect(globalState.value).toBe(0);
});
// 正确 - 清理状态
import { beforeEach } from "bun:test";
beforeEach(() => {
globalState.value = 0;
});
不要测试实现细节
// 错误 - 测试私有方法
test("私有方法", () => {
const instance = new MyClass();
expect(instance._privateMethod()).toBe(true);
});
// 正确 - 测试公共API
test("公共行为", () => {
const instance = new MyClass();
const result = instance.publicMethod();
expect(result).toBe(expectedValue);
});
不要编写不稳定测试
// 错误 - 依赖时间的测试
test("不稳定测试", () => {
setTimeout(() => {
expect(value).toBe(10);
}, 50); // 可能在慢速系统上失败
});
// 正确 - 确定性测试
test("可靠测试", async () => {
await performAsyncOperation();
expect(value).toBe(10);
});
相关技能
- bun-runtime: 核心Bun运行时API和功能
- bun-package-manager: 管理测试依赖项
- bun-bundler: 为不同环境构建测试文件