如何用rewire/proxyquire Stub独立的module.exports函数?
解决直接导出函数的模块Stub问题(rewire/proxyquire方案)
你的问题核心在于直接导出函数的模块和解构导入导致的引用无法被Stub的问题,下面分两种工具给出具体解决方案:
一、用rewire解决
你之前的错误是直接rewire了service模块,但实际上需要rewire的是控制器模块(因为控制器里引入了service函数),通过修改控制器内部的sendOTPOnPhone变量来替换成Stub:
调整测试代码:
const expect = require('chai').expect const request = require('supertest') const sinon = require('sinon') const rewire = require('rewire') const app = require('../../src/app') // 用rewire加载控制器模块 const sendOTPController = rewire('../../src/controllers/MyController') // 替换成你的控制器实际路径 describe('GET /api/v1/auth/otp/generate', function () { it('should generate OTP', async () => { // 创建Stub函数(注意用resolves,因为原函数是async) const sendOTPStub = sinon.stub().resolves({ error: null, data: "OTP sent" }) // 替换控制器内部的sendOTPOnPhone变量 sendOTPController.__set__('sendOTPOnPhone', sendOTPStub) const result = await request(app) .get('/api/v1/auth/otp/generate?phone=8576863491') .set('Accept', 'application/json') .expect('Content-Type', /json/) .expect(200) expect(sendOTPStub.calledOnce).to.be.true expect(result.body).to.deep.equal({ error: null, data: "OTP sent" }) }) })
为什么之前的写法无效?
你的service模块直接导出了匿名函数,模块内部并没有名为sendOTPOnPhone的变量,所以sendOTPOnPhone.__set__('sendOTPOnPhone', stub)根本找不到要修改的目标。而rewire控制器模块后,我们修改的是控制器里通过require引入的那个sendOTPOnPhone变量,这样控制器调用的就是我们的Stub了。
二、用proxyquire解决
proxyquire更适合直接替换模块依赖,不管模块是导出函数还是对象,都能轻松处理:
测试代码调整(单独存储Stub):
const expect = require('chai').expect const request = require('supertest') const sinon = require('sinon') const proxyquire = require('proxyquire') describe('GET /api/v1/auth/otp/generate', function () { it('should generate OTP', async () => { // 创建Stub函数 const sendOTPStub = sinon.stub().resolves({ error: null, data: "OTP sent" }) // 动态加载app并替换service模块的导出 const app = proxyquire('../../src/app', { // 路径要和app中引入service的相对路径一致,或用绝对路径 '../../src/services/OtpService/sendOTPOnPhone': sendOTPStub }) const result = await request(app) .get('/api/v1/auth/otp/generate?phone=8576863491') .set('Accept', 'application/json') .expect('Content-Type', /json/) .expect(200) expect(sendOTPStub.calledOnce).to.be.true expect(result.body).to.deep.equal({ error: null, data: "OTP sent" }) }) })
关于解构导入的问题
你提到用const { sendOTPOnPhone } = require('../sendOTPService')会导致Stub失效,这是因为解构操作会把属性值复制到局部变量,之后即使你替换了模块导出对象的sendOTPOnPhone属性,局部变量还是原来的函数引用。
解决办法有两种:
- 不要解构:直接导入整个模块对象,调用时用
otpService.sendOTPOnPhone(),这样Stub模块导出对象的属性时,调用的就是新的Stub函数:// 控制器里改成 const otpService = require('../../src/services/OtpService/sendOTPOnPhone') // 调用时 const { error, data } = await otpService.sendOTPOnPhone(req.query.phone) - 用rewire/proxyquire替换局部变量:就像上面的方案,直接替换控制器里的那个解构后的局部变量,不过这种方式需要依赖rewire或proxyquire的能力。
内容的提问来源于stack exchange,提问作者Sujeet
相关产品推荐
相关产品推荐

