6.1 最小测试
// sum.test.ts
import { expect, test } from "bun:test";
test("1 + 1 = 2", () => {
expect(1 + 1).toBe(2);
});
$ bun test
bun test v1.2.x
sum.test.ts:
✓ 1 + 1 = 2 [0.5ms]
1 pass
0 fail
Ran 1 test across 1 file. [12ms]
默认匹配 *.test.{js,ts,tsx} / *.spec.{...} / __tests__/ 下的文件。
6.2 describe / it / expect
import { describe, it, expect, beforeEach, afterAll } from "bun:test";
describe("User", () => {
let user;
beforeEach(() => { user = { name: "Alice", age: 30 }; });
afterAll(() => { /* 全组跑完清理 */ });
it("has name", () => expect(user.name).toBe("Alice"));
it("adult", () => expect(user.age).toBeGreaterThanOrEqual(18));
});
生命周期钩子齐全:beforeAll/afterAll/beforeEach/afterEach。
6.3 expect 匹配器
与 Jest API 一致(部分常用):
expect(x).toBe(1); // === 严格相等
expect(obj).toEqual({ a: 1 }); // 深度相等
expect(s).toMatch(/^hello/);
expect(arr).toContain("bun");
expect(fn).toThrow(TypeError);
expect(p).resolves.toBe(42); // Promise 解决值
expect(p).rejects.toThrow();
expect(obj).toHaveProperty("x.y", 1); // 嵌套属性
expect(3.14).toBeCloseTo(3.1, 1);
expect(mockFn).toHaveBeenCalledWith("x", 42);
6.4 异步测试
test("fetch user", async () => {
const res = await fetch("https://api.example.com/user/1");
expect(res.status).toBe(200);
const json = await res.json();
expect(json.name).toBeDefined();
});
6.5 Mock 与 Spy
import { mock, spyOn, test, expect } from "bun:test";
const fn = mock((x: number) => x * 2);
fn(3);
expect(fn).toHaveBeenCalledTimes(1);
expect(fn).toHaveBeenCalledWith(3);
expect(fn.mock.results[0].value).toBe(6);
// Spy:替换对象方法,保留原实现或覆盖
const obj = { greet: (n: string) => `hi ${n}` };
const s = spyOn(obj, "greet");
obj.greet("Alice");
expect(s).toHaveBeenCalledWith("Alice");
// 模块 mock
import { mock } from "bun:test";
mock.module("./user", () => ({
getUser: () => ({ id: 1, name: "stub" }),
}));
6.6 Snapshot 测试
test("render", () => {
const tree = renderComponent();
expect(tree).toMatchSnapshot();
});
首次跑生成 __snapshots__/foo.test.ts.snap,之后每次跑对比。手动更新:
$ bun test --update-snapshots
6.7 过滤 / 聚焦
$ bun test user # 只跑路径含 "user" 的
$ bun test --only # 只跑 test.only/describe.only 标记的
$ bun test -t "登录失败" # 按用例名正则匹配
代码里:
test.only("focus", () => {}); // 配 --only 专跑这个
test.skip("skip", () => {}); // 跳过
test.todo("未来"); // 待写
test.each([
[1, 1, 2],
[2, 3, 5],
])("%i + %i = %i", (a, b, expected) => {
expect(a + b).toBe(expected);
});
6.8 覆盖率 coverage
$ bun test --coverage
File | % Funcs | % Lines | Uncovered
------------|---------|---------|----------
sum.ts | 100.00 | 80.00 | 5-7
user.ts | 50.00 | 62.50 | 10-12,17
配置放 bunfig.toml:
[test]
coverage = true
coverageThreshold = { line = 0.8, function = 0.9 }
coverageReporter = ["text", "lcov"]
coverageDir = "coverage"
6.9 与 Jest / Vitest 的对照
| 功能 | Jest | Vitest | bun test |
|---|---|---|---|
| API(describe/it/expect) | ✅ | ✅ | ✅ Jest 兼容 |
| 启动时间 | ~1-3s | ~300ms | < 30ms |
| 原生 TS/JSX | 需 ts-jest/SWC | ✅ | ✅ |
| 并行执行 | workers | workers | 并行 |
| 模块 mock | jest.mock | vi.mock | mock.module |
| 覆盖率 | istanbul | c8/istanbul | 内置 (llvm) |
6.10 preload 脚本
在每个测试文件跑之前先执行某个初始化:
# bunfig.toml
[test]
preload = ["./test-setup.ts"]
// test-setup.ts —— 设全局 mock、建 DB、注册 custom matchers
import { beforeAll } from "bun:test";
beforeAll(() => {
process.env.NODE_ENV = "test";
});
6.11 DOM 测试:happy-dom
测试前端组件时需要 DOM 模拟——Bun 与 happy-dom 结合好,preload 里装上:
// happydom.ts
import { GlobalRegistrator } from "@happy-dom/global-registrator";
GlobalRegistrator.register();
[test]
preload = ["./happydom.ts"]
6.12 小结
bun testAPI 与 Jest 兼容,迁移成本低——大多数 Jest 测试几乎不改代码能跑。- 启动比 Jest 快 100 倍,适合 TDD(快速反馈)。
- 内置 mock/spy/snapshot/coverage,不需要额外包。
- preload + happy-dom 支持 React/Vue 组件测试。