模块化隔离的核心是控制依赖流向并用 mock 替换真实实现,需通过依赖注入、显式导出、顶层 jest.mock() 和精准 spyon 等方式确保测试可控性与隔离性。

在 JavaScript 单元测试中,模块化隔离的核心不是“把模块拆开”,而是**控制模块间的依赖流向,并用 Mock 替换真实实现**。关键在于让被测模块只与可控的模拟对象交互,不触发实际 I/O、网络、数据库或第三方服务。
明确模块边界,导出可 mock 的接口
模块要便于测试,本身就得支持依赖注入或显式导出行为函数。避免在模块内部直接调用不可控的全局或硬编码实例(比如 axios.get 或 fs.readFileSync)。
- 推荐写法:把外部依赖作为参数传入,或通过对象属性暴露(如
service.apiClient = axios),这样测试时可直接替换 - 避免写法:模块内直接
const axios = require('axios')后调用,这会让 Jest.mock() 难以精准拦截 - ESM 环境下尤其注意:用
import * as mod from 'xxx'而非默认导入,否则jest.spyOn(mod, 'fn')可能失败
顶层 mock + 运行时覆盖:Jest 的标准组合策略
Jest 不允许在 it() 内部调用 jest.mock() 来生效新 mock —— 它只在模块加载阶段解析顶层 jest.mock()。因此正确流程是:
- 在测试文件顶部(
describe外或最开始)调用jest.mock('./someModule'),启用自动模拟 - 用
require()(非import)引入该模块,确保能访问其导出的函数引用 - 每个
it()中用jest.spyOn(module, 'methodName').mockImplementation(...)设置专属行为 - 配合
afterEach(() => jest.clearAllMocks())或.mockRestore()清理,防止测试间污染
处理间接调用:确保 mock 引用与运行时一致
当一个模块 A 的函数内部调用了另一个函数 B,而 B 是模块内定义或从同一模块导出的,仅 mock 全局对象或导入对象上的 B 是无效的——因为 A 内部调用的是原始函数引用。
- 解决方式:把 B 显式挂载到模块导出对象上(如
module.exports.B = functionB(){...}),再通过jest.spyOn(require('./A'), 'B')去 mock - 或者重构为依赖注入:A 接收 B 作为参数,测试时传入 jest.fn(),完全绕过模块内部绑定
- 验证是否生效:检查被测逻辑执行后,mock 函数是否被调用(
expect(mockFn).toHaveBeenCalled()),而非只看返回值
按场景选 mock 粒度:函数级、模块级、实例级
不是所有情况都要 mock 整个模块。粒度选择取决于测试目标:
-
函数级 mock:适合替换工具函数(如
uuid.v4、lodash.get),用jest.mock('lodash', () => ({ get: jest.fn() })) -
模块级 mock:适合 API 客户端(如
axios)、文件系统(fs),用jest.mock('axios')+axios.get.mockResolvedValue(...) -
实例级 mock:适合类实例(如数据库连接池、消息队列客户端),在测试中 new 一个 mock 类,或用
jest.fn().mockImplementation(...)构造构造函数
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











