Jest 测试框架速查表 - JavaScript 单元测试 API 大全
测试只有在你失败时能明确指出"到底哪里坏了"才算值得写。本表覆盖 describe/it 分组、toBe 之外的匹配器、异步与 promise 测试、jest.fn/jest.mock 的模块模拟、快照测试与覆盖率输出。重点放在那些能把坏行为定位清楚、而不是给出含糊红光/红字的匹配器与 mock 策略上。想给 JS/TS 代码真加单元覆盖的团队最合适。读完能写出聚焦的断言,并精确 mock 你想隔离的那条接缝。
典型使用场景
面向需要在 Node.js 或浏览器环境里给业务代码写可靠单元测试的开发者,尤其是要处理异步、定时器、模块依赖这些棘手场景时。本表覆盖四层能力:用 test / describe / only / skip 组织用例结构;用 toBe、toEqual、toHaveLength、toMatch 这组匹配器表达断言;用 async 回调、resolves / rejects、done 回调和假定时器处理异步;再用 jest.fn、jest.spyOn、jest.mock 隔离外部依赖,配合 toHaveBeenCalled 系列验证交互,最后用快照与 --coverage / --watch / --bail / -t 组成日常命令。易错点部分集中了 forgot to return 的异步漏等、mock 忘记还原污染后续用例、快照误提交、test.only 忘记删这几类会让 CI 变红的典型事故。
基础测试 6
test("描述", () => { expect(1 + 2).toBe(3) })it("描述", fn)describe("模块", () => { it(...) })test.skip("跳过", () => {})test.only("聚焦", () => {})test.todo("待补充")匹配器 Matchers 8
expect(value).toBe(other)expect(obj).toEqual({ a: 1 })expect(arr).toHaveLength(3)expect(str).toMatch(/regex/)expect(arr).toContain(item)expect(value).toBeTruthy()expect(fn).toThrow("error")expect(value).toBeNull()异步测试 6
test("async", async () => { await fn() })test("promise", () => fn().then(d => expect(d).toBe(1)))expect(promise).resolves.toBe(value)expect(promise).rejects.toThrow("err")test("done", done => { fn(done) })jest.useFakeTimers()钩子函数 Hooks 4
beforeEach(() => setup())afterEach(() => cleanup())beforeAll(() => init())afterAll(() => teardown())Mock 函数 8
const fn = jest.fn()jest.fn().mockReturnValue(42)jest.fn().mockResolvedValue(val)jest.fn().mockImplementation(n => n * 2)jest.spyOn(obj, "method")jest.mock("./module")jest.requireActual("./module")fn.mockClear() / mockReset() / mockRestore()断言与调用验证 5
expect(fn).toHaveBeenCalled()expect(fn).toHaveBeenCalledWith(arg)expect(fn).toHaveBeenCalledTimes(2)expect(fn).toHaveBeenLastCalledWith(arg)expect(obj).toMatchObject({ a: 1 })快照测试与 CLI 7
expect(tree).toMatchSnapshot()expect(obj).toMatchInlineSnapshot()expect(value).toBeInstanceOf(Class)npx jest --coveragenpx jest --watchnpx jest --bailnpx jest -t "用例名"参数矩阵
| 参数 | 作用 | 示例 |
|---|---|---|
test("描述", fn) | 定义一个测试用例 | test("描述", () => { expect(1 + 2).toBe(3) }) |
it("描述", fn) | test 的别名,BDD 风格读起来更自然 | it("描述", fn) |
describe("模块", fn) | 把相关用例分组,输出层次更清晰 | describe("模块", () => { it(...) }) |
test.skip | 临时跳过此用例 | test.skip("跳过", () => {}) |
test.only | 只运行此用例,调试用 | test.only("聚焦", () => {}) |
test.todo | 占位待实现的用例 | test.todo("用例名") |
expect(v).toBe(x) | 严格相等(Object.is) | expect(value).toBe(other) |
expect(obj).toEqual({ a: 1 }) | 深度相等,递归比较 | expect(obj).toEqual({ a: 1 }) |
expect(arr).toHaveLength(3) | 断言数组或字符串长度 | expect(arr).toHaveLength(3) |
expect(str).toMatch(/regex/) | 正则匹配字符串 | expect(str).toMatch(/regex/) |
expect(arr).toContain(item) | 断言数组包含某元素 | expect(arr).toContain(item) |
expect(v).toBeTruthy() / toBeFalsy() | 真值 / 假值判断 | expect(value).toBeTruthy() |
expect(fn).toThrow("error") | 期望抛出异常,可匹配消息 | expect(fn).toThrow("error") |
expect(v).toBeNull() | 空值判断,另有 toBeUndefined/toBeDefined | expect(value).toBeNull() |
expect(v).toBeInstanceOf(C) | 实例类型断言 | expect(value).toBeInstanceOf(Class) |
expect(obj).toMatchObject({ a: 1 }) | 对象部分匹配,只比对指定字段 | expect(obj).toMatchObject({ a: 1 }) |
test("async", async () => {...}) | async/await 写法,Jest 自动等待 | test("async", async () => { await fn() }) |
() => fn().then(d => expect(d).toBe(1)) | 返回 Promise 让 Jest 等待其落定 | test("promise", () => fn().then(d => expect(d).toBe(1))) |
expect(p).resolves.toBe(v) | 断言 resolve 的值 | expect(promise).resolves.toBe(value) |
expect(p).rejects.toThrow("err") | 断言 reject 的原因 | expect(promise).rejects.toThrow("err") |
test("done", done => {...}) | done 回调写法,需手动调用 | test("done", done => { fn(done) }) |
jest.useFakeTimers() | 启用假定时器,替换真实计时 | jest.useFakeTimers() |
jest.runAllTimers() | 把挂起的假定时器全部执行完 | jest.runAllTimers() |
beforeEach / afterEach | 每个用例前后各执行一次 | beforeEach(() => setup()) |
beforeAll / afterAll | 所有用例前后各执行一次 | beforeAll(() => init()) |
jest.fn() | 创建 Mock 函数 | const fn = jest.fn() |
jest.fn().mockReturnValue(42) | 设置固定返回值 | jest.fn().mockReturnValue(42) |
jest.fn().mockResolvedValue(v) | 返回一个 resolve 的 Promise | jest.fn().mockResolvedValue(val) |
jest.fn().mockImplementation(fn) | 自定义 mock 的实现逻辑 | jest.fn().mockImplementation(n => n * 2) |
jest.spyOn(obj, "method") | spy 对象方法,默认保留原实现 | jest.spyOn(obj, "method") |
jest.mock("./module") | 自动 mock 模块的所有导出 | jest.mock("./module") |
jest.requireActual("./module") | 在 mock 上下文里取真实模块 | jest.requireActual("./module") |
mockClear / mockReset / mockRestore | 清理调用记录、置空实现、恢复原方法 | fn.mockClear() / mockReset() / mockRestore() |
toHaveBeenCalled() | 验证函数被调用过 | expect(fn).toHaveBeenCalled() |
toHaveBeenCalledWith(arg) | 验证调用参数 | expect(fn).toHaveBeenCalledWith(arg) |
toHaveBeenCalledTimes(2) | 验证调用次数 | expect(fn).toHaveBeenCalledTimes(2) |
toHaveBeenLastCalledWith(arg) | 验证最后一次调用的参数 | expect(fn).toHaveBeenLastCalledWith(arg) |
toMatchSnapshot() | 创建或对比快照 | expect(tree).toMatchSnapshot() |
toMatchInlineSnapshot() | 内联快照,写入测试文件本身 | expect(obj).toMatchInlineSnapshot() |
npx jest --coverage | 生成覆盖率报告 | npx jest --coverage |
npx jest --watch | 监听改动只重跑相关测试 | npx jest --watch |
npx jest --watchAll | 监听所有测试文件 | npx jest --watchAll |
npx jest --bail | 首个失败立即停止 | npx jest --bail |
npx jest -t "用例名" | 按用例名正则过滤运行 | npx jest -t "用例名" |
易错点与避坑指南
现象用例总是通过,哪怕把断言里的值改成明显错误的,测试也不报错。
原因异步测试体没有 return 或 await,Jest 在 Promise 落定前就判定用例结束,断言根本没被执行。
处置把用例函数声明为 async 并对异步调用加 await,或直接 return 那个 Promise,让 Jest 正确等待。
现象单独跑某个用例通过,整套一起跑就失败,且失败点飘忽不定。
原因用例之间存在共享状态:上一个用例留下的 spy、mock 或全局变量没有清理。
处置在 afterEach 里调用 mockRestore() / mockClear() 恢复被替换的方法,或用 jest.resetModules() 清理模块缓存。
现象CI 上莫名其妙冒出一批新的失败快照,本地却是绿的。
原因代码改动导致的结构变化本该更新快照,但快照被连同旧值一起提交,CI 比对失败。
处置确认改动是有意的后本地跑一遍并用 -u 更新快照,把新快照一起提交;不要在 CI 里盲目加 -u。
现象提交后其他同事的测试大量莫名失败,怀疑自己改了公共配置。
原因调试时留下的 test.only 只运行被聚焦的用例,其余用例被静默跳过,合入主干后集中暴露。
处置提交前全局搜索 test.only 与 describe.only 确认已清除;也可以用 CI 参数确保跳过逻辑不生效。
现象定时器相关代码的测试永远不结束,跑到超时才报失败。
原因真实定时器需要真实时间等待,甚至配合网络请求导致测试挂起。
处置改用 jest.useFakeTimers() 替换真实计时器,再用 jest.runAllTimers() 同步推进,测试即可瞬时完成。
现象mock 掉整个模块后,被测代码拿到的全是空实现,测试报 undefined。
原因jest.mock 会把模块所有导出替换成 jest.fn(),默认返回 undefined。
处置用 mockReturnValue / mockResolvedValue 补上需要的返回值;若只要部分真实行为,用 jest.requireActual("./module") 混入真实实现。
现象用 toBe 比较两个内容相同的对象,断言失败。
原因toBe 是引用相等,对象每次构造都是新引用。
处置改用 toEqual 做深度比较;只关心部分字段时用 toMatchObject。
排障路径
1按用例名过滤快速定位失败测试
npx jest -t "用例名"只运行名字匹配该正则的用例,避免整轮跑动浪费时间。
2首个失败即停,先看最关键的问题
npx jest --bail一旦出现失败立即停止运行,输出指向第一个破坏链路的位置。
3生成覆盖率报告找出未测分支
npx jest --coverage输出按文件列出语句与分支覆盖率,未覆盖行会直接列出。
4更新已确认变化的快照
npx jest -u把过期的快照写入新值;提交前应人工核对 diff 确认变化是有意的。
5验证 mock 是否被正确调用
expect(fn).toHaveBeenCalledWith(arg)失败信息会打印实际调用参数,可直接对照期望修正实现或断言。
6同步推进假定时器让异步用例不超时
jest.useFakeTimers(); jest.runAllTimers()两个调用配合使用,把挂起的定时器回调全部执行完再断言。
命令示例
异步测试:return / await / resolves 三种等价写法
test("resolves", () => save().then((d) => expect(d).toBe(1)));
test("async/await", async () => {
const d = await save();
expect(d).toBe(1);
});
test("resolves matcher", () => {
expect(save()).resolves.toBe(1);
expect(save()).rejects.toThrow("failed");
});漏掉 await/return 时 Jest 会在 Promise 完成前就判定通过,断言根本没执行——失败测试却显示绿色。用 async + await 写最直观;只断言单个值时 resolves/rejects 更短。
Mock:固定返回值、异步返回值与调用断言
const send = jest.fn();
send.mockResolvedValue({ ok: true });
await handler(send);
expect(send).toHaveBeenCalled();
expect(send).toHaveBeenCalledTimes(2);
expect(send).toHaveBeenLastCalledWith({ id: 7 });
send.mockClear();只断言「被调用过」无法区分调用了几次、参数对不对;toHaveBeenCalledTimes 与 toHaveBeenLastCalledWith 才是真正拦住回归的两道。测试之间复用同一个 mock 时必须 mockClear,否则计数会跨用例累加。
部分 mock:spyOn 保留原实现,requireActual 取真实模块
const spy = jest.spyOn(api, "fetch");
spy.mockResolvedValue({ data: [] });
// 需要其余导出仍是真实实现时
const real = jest.requireActual("./module");
afterEach(() => {
spy.mockRestore();
});jest.mock("./module") 会把整个模块的导出全换成空实现,连你没想替换的工具函数也变 undefined。只替换一个方法时用 spyOn,并在 afterEach 里 mockRestore,否则它会泄漏到后续所有测试。
假定时器:让 debounce/重试逻辑瞬间跑完
jest.useFakeTimers();
test("debounce fires once", () => {
const spy = jest.fn();
debounce(spy, 300);
debounce(spy, 300);
jest.runAllTimers();
expect(spy).toHaveBeenCalledTimes(1);
});不用假定时器就只能真的等 300ms,测试既慢又不稳(机器慢一点就偶发失败)。启用后 runAllTimers 把待执行的定时器一次跑完;但记得测试结束后恢复真实定时器,否则后续用例里的真实定时逻辑全部挂起。
生命周期钩子把 setup/cleanup 收成一处
beforeAll(() => (server = startServer()));
beforeEach(() => setup());
afterEach(() => cleanup());
afterAll(() => teardown());把初始化/销毁放在钩子里而不是复制进每个 it,能避免「新加一个测试忘了清理」这类漏网——漏了清理往往不报错,而是污染下一个用例,出现只在单跑时通过的幽灵 bug。
常见坑与注意事项
- 漏写 await 或 return 时 Jest 会在 Promise 完成前结束用例,断言从未执行却显示通过——异步逻辑的失败被静默吞掉,CI 上极难发现。
- jest.fn() / jest.spyOn 生成的 mock 不自动清理,复用同一个 mock 会让 toHaveBeenCalledTimes 跨用例累加;需在 afterEach 里 mockClear 或 mockReset。
- jest.spyOn 只在当前用例里替身,若忘记 mockRestore,替换会持续影响同一 worker 内后续所有测试文件里对该模块的引用,报错指向毫不相干的用例。
- toMatchSnapshot 的快照一旦过期就该显式用 -u 更新;为了让测试变绿而无脑更新快照,会把真实的行为变更一起固化进去。
- useFakeTimers 之后未恢复真实定时器,后续用例中依赖 setTimeout / setInterval 的代码(含第三方库初始化)会永久挂起,表现为整片用例超时。
- test.only 提交前忘记删除,会让 CI 只跑这一个用例,其余测试全部不执行——绿色流水线不代表代码没问题。
提示
- jest --coverage 生成覆盖率报告,--watch 开启监听模式。
- jest.mock() 会自动 Mock 模块的所有导出,返回 jest.fn()。
- toMatchSnapshot() 适合 UI 组件和序列化结构测试,更新快照加 -u 参数。
- jest.spyOn(obj, "method") 可以 spy 对象方法而不完全 Mock。
- useFakeTimers() 配合 runAllTimers() 可同步测试定时器相关逻辑。
- test.only 调试完记得删除,否则会跳过其他测试用例。
常见问题
异步测试写完却总是立刻通过,漏掉了失败?
这是最常见的假阳性:测试体既没写 async 也没 return Promise,Jest 认为用例同步结束直接判过,Promise 的 rejection 变成了无人处理的错误。正确写法有两种:一是 test("async", async () => { await fn() }),二是 test("promise", () => fn().then(d => expect(d).toBe(1))),显式 return 让 Jest 等待。第三种是 done 回调写法 test("done", done => { fn(done) }),必须手动调用 done,漏调会超时。断言异步结果更推荐 expect(promise).resolves.toBe(value) 和 expect(promise).rejects.toThrow("err"),失败信息比在 then 里写 expect 清楚得多。
mock 出来的函数怎么验证「调用方式和次数」?
jest.fn() 创建的 Mock 函数会记录每次调用,可以直接断言三件事:expect(fn).toHaveBeenCalled() 验证至少被调用过;expect(fn).toHaveBeenCalledWith(arg) 验证某次调用带了指定参数;expect(fn).toHaveBeenCalledTimes(2) 验证调用次数,需要精确计数时优先用它;expect(fn).toHaveBeenLastCalledWith(arg) 只看最后一次调用的参数。返回值侧用 mockReturnValue(42)、mockResolvedValue(val)、mockImplementation(n => n * 2) 配置。断言之间记得用 mockClear() 清调用记录、mockReset() 连实现一起清、mockRestore() 还原被 spy 的原方法,避免测试间串味。
定时器逻辑为什么跑不过去,改成假定时器就行了吗?
setTimeout、setInterval 这类异步等待会让测试变慢甚至挂住。jest.useFakeTimers() 把这些定时器替换成可手动推进的假实现,之后必须主动调用 jest.runAllTimers() 把所有待执行的回调一次性跑完(要指定挂起时长可配合 jest.advanceTimersByTime(ms)),否则断言会在定时器触发之前就执行从而失败。两个前提:假定时器应在测试开始前启用,并记得在测试结束后恢复,否则后续依赖真实计时的用例会全部卡住;对第三方库中的定时器,fake timers 是否生效取决于该库是否使用全局的 setTimeout 而非自己保存的引用。
jest.mock 和 jest.spyOn 各自什么时候用?
jest.mock("./module") 作用于整个模块:所有对该模块的导入都被替换成自动 Mock,返回值是 jest.fn(),测试里再 mockReturnValue 设定行为。适合把外部依赖整体隔离出去。要在 Mock 上下文里访问真实实现,用 jest.requireActual("./module")。jest.spyOn(obj, "method") 则是局部手段,默认保留原实现,你可以在调用后再通过 expect(spy).toHaveBeenCalled() 观察调用,同时选择继续走真逻辑或用 mockImplementation 覆盖。经验法则是:需要验证「有没有被调用」用 spyOn,需要彻底切断依赖行为用 jest.mock;若用了 mockReturnValue 之外的方式还需清理,见 mockRestore。
快照测试什么时候值得用,怎么更新快照?
toMatchSnapshot() 把序列化结果首次运行时写成 .snap 文件,之后每次比对;UI 组件和大段序列化结构用它最省事,因为逐个属性写断言成本高且容易漏。更新用 -u 参数:npx jest -u 重新写入已变化的快照;加到 test.only 上配合 npx jest -t "用例名" 可以先聚焦单个用例再更新,范围可控。风险在于快照可能掩盖回归,所以快照要人工 review 差异再提交。想让快照跟着代码走就用 toMatchInlineSnapshot(),结果直接写回测试文件,评审 diff 时能立刻看出结构变化。
由 巧匠 维护
公开更新于 2026年7月21日,内容持续校对官方文档。