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

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

解决方案

问题根源

  1. bar是模块内部私有函数,未被导出,因此import * as module得到的对象中不存在bar属性;
  2. ES模块的import对象是不可扩展的,无法直接添加或修改属性;
  3. 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内部函数:

  1. 安装依赖:
npm install @sinonjs/esm-module-utils --save-dev
  1. 修改测试代码:
// 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 13:43:11