Jest模拟模块的关键在于模块加载方式而非规范:CommonJS开箱即用,ESM需显式配置;mock路径必须与被测代码中import/require路径完全一致。

在 Jest 中模拟模块,关键不在于“模块规范”,而在于 模块如何被加载和解析。Jest 默认基于 CommonJS(Node.js 原生模块系统)运行测试,因此对不同模块规范的支持方式有本质差异:ESM(import/export)需显式配置才能启用,而 CommonJS(require/module.exports)开箱即用。模拟行为是否生效、写法是否正确,取决于模块实际的导出形式与 Jest 的模拟机制是否匹配。
CommonJS 模块:mockImplementation 和 jest.mock 直接生效
这是 Jest 最稳定、最推荐的模拟场景。Jest 原生支持 CommonJS,jest.mock() 会自动替换 require() 调用目标模块的整个 module.exports 对象。
- 若模块导出一个函数:
module.exports = function foo() { ... },可用jest.fn()替换并设置返回值 - 若模块导出一个对象:
module.exports = { a: 1, b: () => {} },可用jest.mock('./mod', () => ({ a: 99, b: jest.fn() }))或jest.requireActual()部分代理 - 注意:
jest.mock()必须在模块顶层调用(不能包裹在describe或it内),否则可能被 hoisted 失效或延迟执行
ESM 模块(import/export):必须启用实验性 ESM 支持
Jest 默认不解析 import 语句为 ESM——它会把 .js 文件当作 CommonJS 处理,除非你明确开启 type: "module" 或使用 esm 配置。启用后,模拟方式发生变化:
-
jest.mock('./mod.js')不再自动工作;必须配合__esModule: true和默认导出处理(如default: jest.fn()) - 推荐用
jest.unstable_mockModule()(Jest 28+)动态模拟 ESM 模块,它返回 Promise,需await导入测试模块 - 例如:
await jest.unstable_mockModule('./api.js', () => ({ default: jest.fn().mockResolvedValue({ data: 'ok' }) })) - 混合导出(
export const x = 1; export default fn;)需完整模拟命名导出 + 默认导出对象
动态导入(import())与条件加载:需手动控制加载时机
当代码中使用 import('./mod.js') 动态导入时,Jest 不会在静态分析阶段捕获该依赖,jest.mock() 默认无效。此时需:
立即学习“Java免费学习笔记(深入)”;
- 在测试前用
jest.mock('./mod.js', ...)提前声明(即使未静态引入),Jest 仍会拦截后续所有import() - 或在
import()调用后立即await并手动替换其导出(适合一次性 mock 场景) - 对基于环境变量或运行时路径的条件导入(如
import(`./locales/${lang}.js`)),可 mock 整个import()函数本身:jest.mock('module', () => jest.fn().mockResolvedValue({ default: mockData }))
第三方库与 CJS/ESM 混合包:查清实际导出格式再 mock
很多 npm 包(如 lodash-es、axios)同时提供 ESM 和 CommonJS 入口,但 Jest 加载行为取决于你的项目配置(package.json 中的 "type"、"exports" 字段及 Jest 的 resolver)。实操建议:
- 运行
jest --show-config | grep resolver查看 Jest 实际解析路径 - 在测试中
console.log(require.resolve('xxx'))确认加载的是.cjs还是.mjs - 对
axios这类常用库,优先使用jest.mock('axios')(它导出的是 CommonJS 对象),而非尝试 mockimport axios from 'axios'的 ESM 形式 - 避免盲目 mock
node_modules下的 ESM-only 包(如某些现代工具库),改用msw或fetch-mock拦截网络请求更可靠
不复杂但容易忽略:模块模拟是否成功,最终取决于 被测代码里 import/require 的路径是否与 mock 路径完全一致(包括扩展名、大小写、别名),以及模块加载时的上下文是否匹配 Jest 的模块系统设定。










