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

为何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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 23:48:15