根本原因是esm模块加载机制与vscode调试器环境隔离冲突:未正确配置package.json的"type": "module"、launch.json的runtimeargs和env.node_options,以及stub位置不当,导致stub在commonjs上下文失效。

为什么Node测试框架在VSCode里stub不生效?
根本原因不是stub写错了,而是ESM模块加载机制与测试运行器的环境隔离冲突。当你在VSCode终端里用npm test能跑通,但用调试器(F5)或Code Runner点▶️就报ReferenceError: require is not defined或stub完全没被调用——这说明测试进程没走你预期的ESM兼容路径,而是降级到了CommonJS上下文,或者模块解析被VSCode的Node.js调试器绕过了。
检查package.json的type字段和test脚本入口
ESM桩函数(比如sinon.stub()或jest.mock())依赖模块加载器识别import语义。如果项目是ESM但package.json里没声明"type": "module",Node.js会按CommonJS处理.js文件,导致import.meta.url不可用、require被禁用、动态import()失败——所有这些都会让基于ESM重写的stub逻辑直接跳过。
- 确认
package.json根目录有"type": "module"(不是"module"或"exports"字段) - 测试脚本(如
test字段)必须显式指定ESM执行器:"test": "node --experimental-vm-modules -r ts-node/register/transpile-only ./node_modules/vitest/dist/cli.mjs"(Vitest)或"test": "node --loader ts-node/esm ./src/test.spec.ts"(Jest + ts-node) - 若用
vitest,确保vitest.config.ts中environment: 'node'且resolve.alias没把__mocks__映射错
launch.json里必须传--loader或--experimental-vm-modules
VSCode默认的Node.js调试器(node)不自动启用ESM支持。即使你在终端里加了--loader,F5调试时它仍按传统CommonJS启动,stub所在的import链会被提前切断。
微软正式发布 Visual Studio Code 1.118 版本 。本次更新重点强化了 AI 开发体验与企业管理能力,其中最引人注目的是新增 Copilot CLI 远程控制功能,允许开发者通过手机或网页远程监控和接管 AI 会话 。同时,为了提高 AI 的运行性价比,新版本优化了令牌缓存策略以降低成本 。此外,1.118 版还引入了 Chronicle 本地历史追踪、TypeScript 7.0 支持以及更严格的企业级访问管控 。
- 在
.vscode/launch.json中,configurations下对应测试的条目必须包含"runtimeArgs":
{
"type": "node",
"request": "launch",
"name": "Test with Vitest",
"program": "${workspaceFolder}/node_modules/vitest/dist/cli.mjs",
"runtimeArgs": ["--loader", "ts-node/esm"],
"env": { "NODE_OPTIONS": "--loader ts-node/esm" },
"console": "integratedTerminal"
}
program直接指向.spec.ts文件——ESM调试器无法直接加载TS源码,必须经由CLI入口jest,runtimeArgs应为["--experimental-vm-modules"],且jest.config.cjs需导出module.exports = { ... }(不能用ESM export default)stub位置必须在import之后、test执行之前
ESM的import是静态且提升的,而stub(尤其jest.mock())是动态副作用。如果你把stub写在import语句前面,或放在describe()外但没用jest.mock()的自动hoist机制,它会在模块初始化阶段被忽略。
- 对
jest:stub必须写在import之后、describe()之前,且用jest.mock('path/to/module')(字符串字面量,不能是变量) - 对
vitest:用vi.mock('path/to/module', () => ({ ... })),且必须放在顶层作用域(不能包裹在beforeEach里) - 避免在
test()内部调用vi.stubGlobal()或sinon.stub()来mock模块导出——ESM模块对象是只读的,必须在模块加载前拦截 - 验证是否生效:在测试文件开头加
console.log(import.meta.url),调试时看输出路径是否含.mjs或file://协议;若仍是node:internal/modules/cjs/loader,说明没走ESM路径
最常被忽略的是launch.json里漏掉env.NODE_OPTIONS——仅靠runtimeArgs不够,VSCode调试器有时会忽略loader参数,必须双保险传入环境变量。另外,ts-node的esm loader要求tsconfig.json中"module": "nodenext"或"module": "preserve",否则TS编译器仍按CommonJS输出,stub就永远插不到真实模块上。










