JS单元测试进阶需踩准四台阶:环境跑通→函数级验证→行为隔离→工程化闭环;分别对应快速启动、输入输出覆盖、mock拆解依赖、融入开发流程。

从写第一个 expect(add(2, 3)).toBe(5) 到能为复杂组件、异步逻辑、副作用模块稳定护航,JS 单元测试的进阶不是堆时间,而是踩准四个关键台阶:环境跑通 → 函数级验证 → 行为隔离 → 工程化闭环。
第一步:三分钟跑通第一个测试,建立正反馈
别被配置吓住。用 Jest 就是最快路径:
- 执行
npm install --save-dev jest,再运行npx jest --init,全程按提示选 jsdom 环境 和 生成 setup 文件 - 在
package.json里加脚本:"test": "jest",再加个"test:watch": "jest --watch"方便边改边看 - 写一个纯函数(比如
sum(a, b)),新建同名文件sum.test.js,内容就两行:import { sum } from './sum'; test('sums two numbers', () => expect(sum(1, 2)).toBe(3)); - 运行
npm test,看到绿色 ✔ 就算通关——这一步的目标不是写多好,是亲手确认“测试真的能动”
第二步:聚焦输入输出,练熟函数与类的验证逻辑
此时重点不是覆盖率数字,而是建立“测什么”的直觉:
- 对每个函数,至少覆盖:正常输入、边界值(0、负数、空字符串)、错误输入(如除零、非法类型)
- 对类方法,区分“返回值”和“状态变更”:比如
cart.addItem(item)要测 是否真把 item 加进数组(查cart.items.length),而不是只测它没报错 - 避免测试实现细节:不写
expect(cart.items.push).toHaveBeenCalledTimes(1),而写expect(cart.items).toContain(item)——前者绑死内部调用方式,后者只关心结果 - 用
describe分组、it/ test命名描述行为,例如describe('filterUsers', () => { it('returns empty array when input is empty', ...)
第三步:用 mock 拆解依赖,真正隔离单元
真实代码不会只有数学运算。卡点往往在外部交互上:
- 网络请求:用
jest.mock('axios'),然后axios.get.mockResolvedValue({ data: [...] }),让测试不发真实请求 - 定时器:
jest.useFakeTimers()+jest.runAllTimers(),秒级等待变瞬时执行 - 全局对象:比如测试中用了
localStorage,就在setupTests.js里 mock 它:global.localStorage = { getItem: jest.fn(), setItem: jest.fn() } - 关键原则:mock 只用于**不可控或慢/不稳定**的依赖;自己写的模块,优先通过参数传入(依赖注入),而不是在函数里直接
import
第四步:融入开发流程,让测试活起来
熟练 ≠ 会写单个测试,而是让测试成为日常节奏的一部分:
- 提交前必跑:在
package.json的precommit脚本里加jest --coverage --bail,失败直接阻断提交 - 关注覆盖率报告中的 未覆盖分支,而不是总百分比——
if (x > 0) {...} else {...}两边都得有测试,否则逻辑就悬空了 - 给组件写测试时,用
@testing-library/react替代直接操作 DOM:写fireEvent.click(screen.getByText('Submit')),再断言expect(screen.getByText('Success')).toBeInTheDocument(),更贴近用户真实操作 - 定期删掉过时的 mock 或断言——当接口改了、字段变了,旧测试挂掉是好事,说明它还在起作用


















