commonjs 单元测试中 mock 失效的根源是 require.cache 缓存机制;需在 beforeeach 中先清除被测模块及依赖的缓存,再 require 以触发重新加载,配合 vi.mock 实现有效 mock。

在 CommonJS 环境下做单元测试时,模块缓存(require.cache)是导致 mock 失效的根源之一。因为 require 一旦加载过某个模块,后续调用会直接返回缓存中的 module.exports,不会重新执行模块代码——这使得 vi.mock 或手动替换无法影响已加载的依赖。
理解 require.cache 的作用机制
Node.js 将每个已加载的模块路径映射到一个 Module 实例,存于 require.cache 对象中。它的键是模块的绝对路径(由 require.resolve 返回),值是该模块的 module 对象,其中 exports 是对外暴露的内容。
只要缓存存在,require('./xxx') 就永远返回同一份导出对象,哪怕你改了源文件或试图重赋值 exports。
清除缓存并强制重新加载模块
要实现真正的模块隔离,必须在每次测试前清除目标模块及其所有依赖的缓存,再触发重新 require。关键步骤如下:
详细的 Three.js 3D 图形参考,涵盖场景设置、相机、几何体、材质、光照、动画、控制器、加载器、数学工具和调试。
- 用
require.resolve('./path')获取模块的绝对路径(避免相对路径解析歧义) - 从
require.cache中删除该路径对应的条目 - 如果该模块依赖其他被 mock 的模块(如
../helpers/aws),也要一并清除它们的缓存 - 在
beforeEach或setup钩子中执行,确保每个测试用例都拿到“干净”的模块实例
配合 vi.mock 使用的正确顺序
Vitest 的 vi.mock 是静态分析阶段生效的,但仅对 ESM 有效;在 CommonJS 中,它无法拦截 require 调用。所以必须手动控制加载时机:
- 先清除缓存(包括被测模块和被 mock 模块)
- 再调用
vi.mock(虽然它本身不生效,但可保留语义一致性) - 最后在测试内部
require目标模块——此时会走全新加载流程,读取 mock 后的导出
示例写法:
beforeEach(() => {<br> const awsPath = require.resolve('../src/helpers/aws');<br> const authPath = require.resolve('../src/client-authenticator');<br> delete require.cache[awsPath];<br> delete require.cache[authPath];<br>});<br><br>it('should use mocked ssmClient', () => {<br> vi.mock('../src/helpers/aws', () => ({<br> ssmClient: vi.fn(),<br> getParameterCommand: vi.fn(),<br> }));<br> const ClientAuthenticator = require('../src/client-authenticator'); // 此时才加载<br> expect(ClientAuthenticator.something()).toBeDefined();<br>});
避免副作用:清理间接依赖缓存
如果被测模块 A require 了 B,B 又 require 了 C,而你想 mock C,那么只清 C 的缓存还不够——B 的缓存里仍持有旧的 C 导出。稳妥做法是:
- 递归遍历
require.cache,找出所有引用了目标模块路径的module.parent - 或更简单:在测试前统一清空整个缓存(仅限单个测试文件内,不影响其他测试):
Object.keys(require.cache).forEach(key => delete require.cache[key]); - 注意:不要在全局
beforeAll清空,否则破坏测试间隔离
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










