学JS单元测试的关键是建立“可验证行为”思维,践行AAA模式、单职责测试、行为命名、数据驱动及I/O隔离。

学 JS 单元测试的高级编程模式,关键不是堆砌技巧,而是建立“可验证行为”的思维习惯。它不依赖框架语法炫技,而靠结构清晰、意图明确、边界干净的实践沉淀出来。
用 AAA 模式固化测试节奏
Arrange-Act-Assert 不是教条,是降低认知负担的阅读协议。人读测试时,最怕在一堆代码里找“它到底在测什么”。AAA 把意图拆成三步:
-
Arrange:只做进入测试场景必需的事——实例化对象、准备输入、mock 外部调用(比如用
jest.fn()替掉 API 请求) - Act:严格控制为 1 行——调用被测函数或方法,不加判断、不连写逻辑
- Assert:也尽量 1 行——直接断言结果,不嵌套 if 或重复取值;多个断言要确保彼此独立,避免一个失败导致后续跳过
让每个 test 只讲一件事
高内聚不是测试之间耦合,是单个测试聚焦一个明确行为。例如校验邮箱格式,就该拆成:
test('returns false for empty string')test('returns true for "user@domain.com"')test('throws on malformed "@" position')
不混入数据库保存、通知发送等下游动作。一旦发现某个 test 里出现多个 expect 且跨职责(比如既查返回值又查 mock 调用次数),就说明它该拆了。
用命名代替注释,把测试当活文档
测试名是你留给未来自己的第一行说明。它应该回答:“这个函数在什么条件下,会产生什么可观察结果?”
- ✅
test('converts "2023-01-01" to Date instance')—— 描述行为,不绑定实现 - ❌
test('uses new Date() constructor')—— 实现细节会变,名字就失效 - ✅
test('throws when rate exceeds 1')—— 明确边界与错误契约 - ❌
test('calls validateRate() then calculate()')—— 暴露内部流程,重构即破
数据驱动替代复制粘贴
对同一函数多组输入输出,别写十个相似 test。用 test.each 统一组织:
test.each([
[0, true],
[2, true],
[1, false],
[-4, true]
])('isEven(%i) returns %s', (input, expected) => {
expect(isEven(input)).toBe(expected);
});
新增测试用例只需加一行数据,逻辑不变、不易漏、易维护。边界值、负数、零、极大数,一目了然。
隔离 I/O,守住“单元”本质
真正的单元测试不能依赖真实网络、数据库、时间或随机数。它们会让测试变慢、不稳定、不可重复。
- HTTP 请求 → mock
fetch或用 MSW 拦截 - 当前时间 → 把
Date.now作为参数传入,测试时固定为new Date('2023-01-01').getTime() - 随机数 → 接收
random = Math.random为参数,测试中传入() => 0.5 - 文件读写 → 用内存模拟(如
jest.mock('fs'))或注入读写函数
不是为了“绕开问题”,而是确保每次运行都在同一可控现实中验证逻辑本身。



















