为何Hapi Web应用中基础Sinon Stub无法正常工作?
问题描述
为Hapi Web应用添加自动化单元测试(使用Lab与Sinon)时,发现GET请求处理器中调用的mytest函数的Sinon Stub未生效:测试时实际执行原函数输出“My test called”,而非Stub函数的“Stubbed mytest called”,导致断言失败。相关代码如下:
GET处理器代码
const { mytest } = require('../services/my-service') module.exports = [ { method: 'GET', path: `${urlPrefix}/healthcheck-detailed`, config: { auth: false }, handler: async (request, h) => { mytest() return h.response('Success').code(200); } }]
my-service模块代码
function mytest(){ console.log("My test called") } module.exports = { mytest }
测试代码
const Lab = require('@hapi/lab'); const Code = require('@hapi/code'); const { expect } = Code; const { it, describe, before, after, beforeEach, afterEach } = exports.lab = Lab.script(); const { init } = require('../../../server/') const sinon = require('sinon') describe('Healthcheck /get', () => { let server let mytestStub beforeEach(async () => { server = await init() // Stub the mytest function directly using the full module path mytestStub = sinon.stub(require('../../../server/services/my-service'), 'mytest').callsFake(() => { console.log("Stubbed mytest called"); }); }) afterEach(async () => { await server.stop() // Restore the original mytest function mytestStub.restore(); }) it('calls the correct functions on /healthcheck-detailed', { timeout: 30000 }, async () => { const response = await server.inject({ method: 'GET', url: '/healthcheck-detailed' }) console.log("Stub called: ", mytestStub.called); expect(mytestStub.calledOnce).to.be.true(); }) });
问题原因与解决方法
核心原因
你在处理器中用解构赋值直接导入了mytest函数,这会把函数引用缓存到处理器模块的局部变量里。后续对my-service模块导出对象的mytest进行Stub时,处理器调用的还是之前缓存的原函数,完全不会用到Stub后的版本——Node.js模块加载时会缓存导出对象,但解构取出的函数是单独的引用,和原模块导出对象上的函数已经脱离关联了。
解决方法
方法1:修改处理器的导入方式(必须)
不要解构导入函数,保留整个模块的引用,调用时通过模块对象访问:
// 保留完整模块引用,不解构 const myService = require('../services/my-service') module.exports = [ { method: 'GET', path: `${urlPrefix}/healthcheck-detailed`, config: { auth: false }, handler: async (request, h) => { // 通过模块对象调用函数 myService.mytest() return h.response('Success').code(200); } }]
这样后续Stubmy-service模块导出对象上的mytest时,处理器调用的就是Stub后的函数。
方法2:调整Stub的执行时机(推荐配合方法1)
把Stub操作放在初始化server之前,因为server初始化时会加载处理器模块,提前Stub能保证处理器加载时拿到的就是Stub后的函数引用:
beforeEach(async () => { // 先Stub,再初始化server mytestStub = sinon.stub(require('../../../server/services/my-service'), 'mytest').callsFake(() => { console.log("Stubbed mytest called"); }); server = await init() })
如果不修改处理器的导入方式,即使提前Stub,处理器还是会用解构缓存的原函数,所以方法1是核心。
验证要点
- 确保处理器始终通过模块对象调用
mytest,而非直接使用解构后的局部函数 - Stub操作必须在server初始化前执行,保证模块加载阶段就拿到Stub后的函数引用
内容的提问来源于stack exchange,提问作者fosbie
相关产品推荐
相关产品推荐

