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

如何将Node.js内部函数设为存根以进行单元测试?

如何在单元测试中隔离Handler的业务逻辑调用(Mocha + Sinon)

问题场景

现有一段Node.js Lambda处理函数,内部会根据条件调用不同的业务逻辑函数:

async function handler(event) {
  await parseEvent(event)

  if(event.a)
    await doBusinessLogicA(event)
  else
    await doBusinessLogicB(event)

  return response
}

async function doBusinessLogicA(event){ /* 实际业务逻辑 */ }
async function doBusinessLogicB(event){ /* 实际业务逻辑 */ }

编写单元测试时,我们只需要验证handler的分支逻辑是否正确,不需要执行doBusinessLogicA或doBusinessLogicB的实际代码。但直接运行测试会触发这些业务逻辑,之前尝试过Sinon沙箱和修改module.exports设置存根,但找不到合适的恢复方式。

解决方案

核心思路是让handler内部调用的业务函数可被Sinon替换,同时确保测试后能恢复原函数,避免影响其他测试用例。

步骤1:重构模块导出

修改原模块,将业务逻辑函数也导出,同时让handler内部引用导出的版本:

async function handler(event) {
  await parseEvent(event)

  // 改为调用exports上的函数,方便后续存根替换
  if(event.a)
    await exports.doBusinessLogicA(event)
  else
    await exports.doBusinessLogicB(event)

  return response
}

async function doBusinessLogicA(event){ /* 实际业务逻辑 */ }
async function doBusinessLogicB(event){ /* 实际业务逻辑 */ }

// 导出所有需要测试/存根的函数
module.exports = {
  handler,
  doBusinessLogicA,
  doBusinessLogicB
}

步骤2:编写带存根的测试用例

使用Sinon创建业务函数的存根,在beforeEach中初始化,afterEach中恢复原函数:

const sinon = require('sinon')
const myModule = require('./location-whatever')

describe("Lambda tests", () => {
  let doBusinessLogicAStub, doBusinessLogicBStub

  // 每个测试前创建存根,模拟业务函数的异步返回
  beforeEach(() => {
    doBusinessLogicAStub = sinon.stub(myModule, 'doBusinessLogicA').resolves()
    doBusinessLogicBStub = sinon.stub(myModule, 'doBusinessLogicB').resolves()
  })

  // 每个测试后恢复所有存根,避免污染后续测试
  afterEach(() => {
    sinon.restore()
  })

  describe("Handler", () => {
    it("Should call doBusinessLogicA when event.a is true", async () => {
      await myModule.handler({a: true})
      // 验证分支逻辑正确,doBusinessLogicA被调用一次
      sinon.assert.calledOnce(doBusinessLogicAStub)
      // 验证另一个分支未触发
      sinon.assert.notCalled(doBusinessLogicBStub)
    })

    it("Should call doBusinessLogicB when event.a is false", async () => {
      await myModule.handler({a: false})
      sinon.assert.calledOnce(doBusinessLogicBStub)
      sinon.assert.notCalled(doBusinessLogicAStub)
    })
  })
})

关键说明

  • 为什么要修改handler内部调用?如果handler直接调用未导出的私有函数,Sinon无法直接替换这些函数,因为它们不在模块的导出对象上。让handler引用导出的函数,就能通过模块对象进行存根替换。
  • sinon.restore()会自动恢复所有由Sinon创建的存根,无需手动逐个恢复,解决了之前的恢复问题。
  • 用resolves()模拟异步函数的成功返回,如果需要模拟错误,可以用rejects(new Error('test error'))。

内容的提问来源于stack exchange,提问作者Riley Shaw

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 14:02:40