使用sinon与babel-plugin-rewire时无法Stub模块内函数的问题排查
你遇到的这个问题并非操作疏漏,而是babel-plugin-rewire的代码转换逻辑与Sinon Stub的工作机制存在兼容性冲突导致的。下面我来详细解释原因,并给出几种可行的解决方案:
问题根源分析
babel-plugin-rewire的核心是通过修改模块内部的函数引用方式,实现对私有变量/函数的重写——它会把模块内的函数调用(比如A()调用同模块的B())转换成通过rewire内部的__get__方法来获取函数,类似这样:
// 原代码 function A() { return B(); } function B() { return 'original'; } // rewire转换后 function A() { return __get__('B')(); } function B() { return 'original'; }
而Sinon Stub的工作逻辑是直接修改导出对象上的函数引用,比如sinon.stub(myModule, 'B')是替换myModule.exports.B的指向。但此时模块内部的A()调用的是__get__('B')返回的原始函数,而非exports上的版本,所以Stub完全无法生效。
甚至单独用Sinon也失效的情况,大概率是因为Babel的@babel/preset-env将CommonJS模块转换成了ES模块格式,Sinon对ES模块的直接Stub支持有限,结合rewire的转换后这个问题被放大了。
可行解决方案
方案1:结合rewire的API与Sinon Stub
既然rewire已经接管了模块内部的函数引用,我们可以直接用rewire的__set__方法,把Sinon创建的Stub注入到模块内部:
const myModule = require('./index.js'); const sinon = require('sinon'); const { expect } = require('chai'); describe('模块内函数测试', () => { it('应该用Sinon Stub替换内部函数', () => { // 创建Sinon Stub const stubbedB = sinon.stub().returns('stubbed result'); // 用rewire的__set__替换模块内部的B函数 myModule.__set__('B', stubbedB); // 调用A函数,此时内部会使用Stubbed的B const result = myModule.A(); expect(result).to.equal('stubbed result'); expect(stubbedB.calledOnce).to.be.true; }); });
这种方式既保留了rewire处理私有依赖的能力,又能利用Sinon的Stub功能。
方案2:调整模块写法,统一函数引用
把模块内的内部函数挂载到exports上,并且模块内部调用时直接引用exports上的版本,这样Sinon就能直接Stub导出的函数:
// index.js 修改后 exports.B = function B() { return 'original value'; }; exports.A = function A() { // 调用exports上的B,而非直接调用B return exports.B(); };
测试时直接Stub导出的B即可:
const myModule = require('./index.js'); const sinon = require('sinon'); const { expect } = require('chai'); describe('模块内函数测试', () => { it('Sinon Stub应该生效', () => { sinon.stub(myModule, 'B').returns('stubbed result'); const result = myModule.A(); expect(result).to.equal('stubbed result'); }); });
这种写法同时兼容rewire和Sinon,因为rewire的__set__也能修改exports.B,而模块内部调用的就是这个引用。
方案3:替换rewire为兼容性更好的工具
如果上述方案都不符合你的需求,可以考虑改用rewiremock或者esmock这类更现代化的模块重写工具,它们的转换逻辑与Sinon的兼容性更好,不需要修改原有模块代码就能实现Stub和私有依赖替换。
总结
这个问题是babel-plugin-rewire的设计机制导致的兼容性问题,不是你的操作错误。根据你的项目场景选择上述任意一种方案,都能解决Stub失效的问题。
内容的提问来源于stack exchange,提问作者Ronan Quigley

