Node.js中如何将模块内函数转为Sinon Stub?
问题:如何在ES Module项目中用Sinon Stub替换模块内部函数?
项目环境与代码
package.json配置
{ "name": "some-module-name-here", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "sinon": "^17.0.0" }, "type": "module" }
Node版本:v20.8.0
模块代码
module.js:
export function foo() { return bar(); } function bar() { return 12; }
测试代码与错误
test.js初始写法:
import * as sinon from "sinon"; import * as module from "./module.js"; sinon.stub(module, "bar");
运行后报错:
[snip]/sinon-esm.js:3849 throw new TypeError("ES Modules cannot be stubbed"); ^
尝试直接赋值修改:
module.bar = sinon.stub();
报错:
[snip]/test.js:6 module.bar = sinon.stub(); ^ TypeError: Cannot add property bar, object is not extensible
解决方案
问题根源
bar是模块内部私有函数,未被导出,因此import * as module得到的对象中不存在bar属性;- ES模块的import对象是不可扩展的,无法直接添加或修改属性;
- Sinon当前版本对ES模块的直接Stub支持有限,无法直接Stub整个模块对象。
方案1:重构模块导出可修改的bar
修改module.js,将bar改为导出的可变变量,让foo调用这个导出版本:
// module.js export let bar = () => 12; export function foo() { return bar(); }
测试代码中直接替换为Stub:
// test.js import * as sinon from "sinon"; import * as module from "./module.js"; const stubbedBar = sinon.stub().returns(42); module.bar = stubbedBar; console.log(module.foo()); // 输出42,Stub生效
方案2:依赖注入重构foo
给foo添加可选参数,默认使用内部bar,测试时传入Stub即可:
// module.js function bar() { return 12; } export function foo(barFn = bar) { return barFn(); }
测试代码:
// test.js import * as sinon from "sinon"; import { foo } from "./module.js"; const stubbedBar = sinon.stub().returns(42); console.log(foo(stubbedBar)); // 输出42
这种方法无需暴露内部函数,更符合封装原则。
方案3:使用Sinon ES模块Seam工具(无需修改业务代码)
如果无法重构原有代码,可借助@sinonjs/esm-module-utils创建模块Seam来Stub内部函数:
- 安装依赖:
npm install @sinonjs/esm-module-utils --save-dev
- 修改测试代码:
// test.js import * as sinon from "sinon"; import { createRequire } from "module"; import { getSeam } from "@sinonjs/esm-module-utils"; const require = createRequire(import.meta.url); const seam = getSeam(require, "./module.js"); // Stub内部的bar函数 seam.stub("bar", () => 42); // 重新加载模块(ES模块静态特性,需重新导入) const module = await import("./module.js"); console.log(module.foo()); // 输出42
内容的提问来源于stack exchange,提问作者kasdeya
相关产品推荐
相关产品推荐

