You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Jasmine时ES6导入与CommonJS引入命名导出的可模拟性差异

Why CommonJS Spy Works But ES6 Import Spy Fails With Babel

Great question—this is a super common gotcha when mixing ES6 modules, CommonJS, and testing spies, especially with Babel's module compilation. Let's break down exactly what's happening here, and why it seems to contradict your initial understanding:

1. CommonJS Module Behavior: Mutable Singleton Exports

When you use require('./fileA') in CommonJS:

  • Node.js executes the module once, caches its exports object, and returns the same cached object to every subsequent require call.
  • The exports object is a plain, mutable JavaScript object. When you run spyOn(A, 'foo') in your test file, you're directly modifying the foo property on this cached exports object.
  • Since commonBaz.js also require('./fileA') and gets the same cached object, any call to A.foo() in that file will use the spy function you created—hence the test succeeds.

This aligns with the "copy" behavior you mentioned, but the key detail is that it's a copy of a reference to the cached exports object, not a copy of the function itself. So modifying the object's properties affects all consumers.

2. Babel-Compiled ES6 Modules: Read-Only Binding Views

ES6 modules spec defines import * as A as a module namespace object—an immutable object where each property is a live binding to the module's exports. Babel tries to simulate this behavior when compiling ES6 modules to CommonJS, but with a critical catch for testing:

  • When you import * as A from './fileA', Babel compiles this to wrap the CommonJS exports object in a structure that enforces read-only properties (to match ES6 spec).
  • spyOn(A, 'foo') attempts to replace the foo property on this namespace object, but since the property is read-only (or implemented via getters that point directly to the original function in fileA.js), the spy doesn't actually override the real foo function.
  • es6Baz.js uses its own import * as A binding, which still points to the original foo function in fileA.js—so the spy in your test file never catches the call.

This seems to contradict your "live binding" intuition, but the issue is that live bindings in ES6 don't let you reassign the binding itself—you can only modify the value if the module exports a mutable value (like an object with methods). Since you're exporting top-level functions, the bindings are read-only.

3. Why Your Initial Intuition Seemed Reversed

You were right that ES6 uses live bindings and CommonJS uses value copies—but in this testing scenario:

  • CommonJS's mutable exports object lets you overwrite the function reference globally (since all consumers share the same cached object).
  • ES6's read-only module namespace objects prevent you from overwriting the binding, so the original function remains in use across all imports.

Quick Fixes for the ES6 Spy Issue

If you want to spy on ES6 exports with Babel, try these options:

  • Export an object containing your functions instead of top-level named exports (so you can spy on the object's mutable methods).
  • Use a module mocking tool designed for ES6 modules (like Jest's jest.mock with ES6 syntax, or babel-plugin-rewire).
  • For test-only builds, tweak Babel config to disable read-only enforcement for module namespace objects (not recommended for production).

内容的提问来源于stack exchange,提问作者Johnny

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 03:54:12