JS单元测试核心是设计即为可测:函数行为确定、依赖可控、边界清晰、验证直接;新项目首选Vitest,因其启动快、零配置、原生支持ESM/TS且API与Jest一致,安装后配test脚本和vitest.config.js即可运行。

JS 中单元测试不是“写完函数再补几个 expect”,而是从代码设计开始就为可测性服务的一整套实践。核心在于:让函数行为确定、依赖可控、边界清晰、验证直接。
选对工具,轻量起步
新项目首选 Vitest——它启动快、零配置、原生支持 ESM 和 TypeScript,API 与 Jest 高度一致,学一个等于掌握两个。安装只需:
npm install -D vitest- 在
package.json中添加:"test": "vitest" - 加一行配置(
vitest.config.js)启用全局 API 和 Node 环境:export default defineConfig({ test: { globals: true, environment: 'node' } })
已有大型 Jest 项目可继续沿用,但新团队不必为“生态惯性”牺牲开发体验。
写真正可测的函数
函数是否容易测,直接反映设计质量。避开三类典型陷阱:
-
隐藏副作用:避免直接调用
Date.now()、Math.random()、localStorage。改为参数传入或依赖注入,例如:function formatTime(clock = Date) { return clock.now(); } -
隐式状态污染:不修改传入对象,返回新对象或明确标注;异步操作必须返回 Promise,不能只靠
setTimeout -
强耦合外部系统:如
fetch调用应抽象为参数,默认使用真实fetch,测试时传入 mock 函数
组织清晰、覆盖关键场景
一个测试文件对应一个模块,用 describe 划分功能域,it 描述具体行为。每条用例遵循“给定-当-那么”逻辑:
- 正常路径:输入有效值,检查输出是否符合预期
-
边界条件:空字符串、零、负数、极大值、
null/undefined -
异常路径:用
expect(() => fn()).toThrow()验证错误抛出 -
交互逻辑:如组件事件触发后,状态是否更新、回调是否被调用(配合
vi.fn())
例如测试加法函数:it('returns 0 when adding -1 and 1', () => { expect(add(-1, 1)).toBe(0); });
用 Mock 隔离外部依赖
单元测试必须“掐断”网络、数据库、定时器等不可控因素。Vitest 提供简洁可靠的模拟机制:
-
vi.fn()创建模拟函数,可设定返回值、记录调用次数和参数 -
vi.mock('./api.js')替换整个模块,返回假数据(注意:Vitest 的 mock 是运行时行为,比 Jest 更贴近直觉) - 测试后调用
vi.clearAllMocks(),防止测试间状态干扰
比如模拟一个用户获取函数:vi.mock('./userApi.js', () => ({ getUser: vi.fn().mockResolvedValue({ id: 1, name: 'Alice' }) }));
调试与持续验证
别靠 console.log 猜执行流。VS Code 中直接右键点击测试用例选择 Debug Test,断点进源码一目了然。同时建议:
- 启用覆盖率报告:
vitest --coverage,关注分支和语句覆盖,而非盲目追求 100% - 把
test命令加入 CI 流程(如 GitHub Actions),每次提交自动运行,失败即阻断 - 测试文件统一用
.test.ts或.spec.js后缀,便于工具识别和 IDE 支持


















