模块化测试中重写导入依赖需在模块解析阶段拦截替换导出,jest 的 jest.mock() 是推荐方式,支持 esm 和 commonjs;vitest 用 vi.mock(),原生 esm 需构建工具配合;路径必须与 import 完全一致。

在模块化测试中重写导入依赖,核心是在测试运行前拦截并替换模块的实际导出,而不是修改源码或等待模块加载完成后再打桩。JavaScript 的模块系统(尤其是 ESM)具有静态导入特性,因此必须在模块解析阶段介入——这决定了不能靠运行时赋值(如 jest.mock() 之外的手动赋值)来覆盖 ESM 默认导出。
使用 Jest 的 jest.mock() 自动模拟
这是最常用且推荐的方式,适用于 CommonJS 和 ESM(Jest 28+ 原生支持 ESM 模拟)。它会在模块被 import 前自动替换目标模块的导出:
- 放在测试文件顶部(必须在任何
import目标模块之前) - 可传入工厂函数,返回自定义 mock 对象
- 对默认导出、命名导出均有效,支持部分 mock(如只 mock 某个函数)
// math.js
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;
<p>// calculator.js
import { add, multiply } from './math.js';
export const calculate = (x, y) => add(x, y) + multiply(x, y);</p><p>// calculator.test.js
import { calculate } from './calculator.js';</p><p>// ✅ 在 import calculator 之后、但 import math 之前?不 —— 实际上 jest.mock 会提升到文件顶部,自动作用于后续所有 import
jest.mock('./math.js', () => ({
add: jest.fn().mockReturnValue(10),
multiply: jest.fn().mockReturnValue(20),
}));</p><p>test('uses mocked math functions', () => {
expect(calculate(2, 3)).toBe(30);
expect(add).toHaveBeenCalledWith(2, 3);
expect(multiply).toHaveBeenCalledWith(2, 3);
});</p>
ESM 环境下手动 mock(无 Jest 时)
若用 Vitest、Native ESM 测试或自建环境,需借助 import.meta.resolve + 动态 import() 或工具链支持(如 Vitest 的 vi.mock),不能直接重赋值 ESM 导出对象(因其只读):
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- Vitest 中用
vi.mock('./math.js', () => ({ add: vi.fn() })),语法与 Jest 高度兼容 - 原生 ESM 中无法直接 mock,需配合构建工具(如 esbuild 插件)或测试运行器的模块拦截能力
- 避免尝试
import * as math from './math.js'; math.add = jest.fn()—— 这会报错:Cannot assign to read only property
按需 mock:只替换部分导出
不必全量覆盖模块,可用 jest.requireActual() 保留原始实现,再覆盖特定函数:
- 适合“仅打桩某一个副作用函数(如 API 调用),其余逻辑走真实代码”场景
- 保持测试更贴近真实行为,减少 mock 维护成本
jest.mock('./api.js', () => {
const actual = jest.requireActual('./api.js');
return {
...actual,
fetchUser: jest.fn().mockResolvedValue({ id: 1, name: 'Mocked' }),
};
});
注意模块路径与解析一致性
mock 的路径必须与 import 语句中的路径完全一致(包括扩展名、别名、相对/绝对):
- 写
./utils就不能 mock./utils.js(即使两者指向同一文件) - 使用 TypeScript 路径映射(
paths)时,mock 路径需匹配tsconfig.json中的解析结果,或使用别名(如@lib/utils)并确保 Jest/Vitest 配置了相应别名映射 - 可通过
jest.config.js的moduleNameMapper统一处理别名
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










