单元测试应验证行为而非实现,隔离依赖、保持独立、表达清晰:只测输入输出,每个it专注一件事,测试数据自包含,依赖显式注入。

JS 单元测试中常见反模式,核心问题不是“测不测”,而是“怎么测”——测实现细节、依赖隐藏、结构混乱、语义模糊,都会让测试从保障变成负担。规避的关键是回归单元测试本质:验证行为、隔离依赖、保持独立、表达清晰。
别测实现,只测行为
很多测试失败不是因为功能出错,而是因为重构后调用路径变了。比如断言“某个私有方法被调用了 2 次”,或 mock 内部 service 并 verify 调用顺序。这类测试把实现逻辑硬编码进用例里,一改就崩。
- 只关注输入和输出:给定用户等级和订单金额,应返回多少折扣;不关心内部是 if-else 还是策略模式
- 避免 verify 内部方法调用,尤其不要 mock 自己类里的方法
- 把“计算过程”当成黑盒,测试边界值、异常输入、典型场景即可(如:0 元订单、VIP 用户、过期优惠券)
一个 it 只做一件事
把多个断言塞进同一个 it 块,表面省事,实则埋雷。一旦第一个 assert 失败,后续逻辑直接跳过,你根本不知道其他幂次是否也错了。
- 每个 it 应有唯一明确的意图,比如 it('pow(5, 1) 返回 5')、it('pow(5, 2) 返回 25')
- 用数据驱动写法减少重复:forEach 或 test.each(Jest/Vitest 支持)
- 失败时能立刻定位具体哪一组输入输出异常,无需反复运行调试
拒绝全局夹具,每个测试自带数据
在 beforeAll 或 beforeEach 里统一插入数据库种子数据,看似高效,实则制造隐式依赖和状态污染。A 测试删了某条记录,B 测试就可能因找不到数据而失败。
- 测试前用工厂函数创建所需数据,测试后立即清理(如 in-memory DB 或 mock 数据库)
- 避免共享 state:不复用对象实例、不修改全局变量、不共用 mock 实例
- 即使性能稍低,换来的是可预测、可并行、可单独重跑的稳定测试
显式依赖,远离 Service Locator
在业务方法里用 ModuleRef.get() 或手写全局容器取服务,会让依赖不可见、不可 mock、不可静态分析。单元测试时无法替换真实依赖,只能走通路或绕大弯 mock 容器本身。
- 所有依赖必须通过构造函数注入,签名即契约
- 参数超过 4 个?考虑拆分职责,或用配置对象/工厂封装创建逻辑
- 测试中只需在 createTestingModule 的 providers 里替换对应 service,干净利落


















