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

如何为调用两个依赖函数的getAppts函数编写正确单元测试?

问题:如何正确测试getAppts函数?

我需要测试当前模块里的getAppts函数,这个函数调用了另外两个函数。请问评估getAppts代码覆盖率的正确方式是什么?是否需要在单元测试中把db.getDatabase()和fetchAppts()都设为stub?我现在的单元测试写得很不完整,甚至可能完全错误。

业务代码

'use strict'

const db = require('../db/db')
const log = require('../utils/logging')(__filename)
const { fetchAppts, fetchApptDetails } = require('../db/dao/appointment-dao')

async function getAppts (queryParams) {
  log.debug('getAppts(): %j', queryParams)
  const knex = db.getDatabase()
  const appointments = await fetchAppts(queryParams, knex)
  return appointments
}

async function getApptDetails (demoApptId) {
  log.debug('getApptDetails(): %j', demoApptId)
  const knex = db.getDatabase()
  const apptDetails = await fetchApptDetails(demoApptId, knex)
  return apptDetails
}

module.exports = {
  getAppts,
  getApptDetails
}

当前单元测试代码

'use strict'

const sinon = require('sinon')
const chai = require('chai')
const chaiAsPromised = require('chai-as-promised')
const dirtyChai = require('dirty-chai')
const sinonChai = require('sinon-chai')
const { expect } = require('chai')
const proxyquire = require('proxyquire')

const apptsResponse = require('../../data/get-appts-response.json')

chai.use(dirtyChai)
chai.use(sinonChai)
chai.use(chaiAsPromised)
chai.should()

describe.only('appointment-service.js', () => {
  let apptService
  let apptDaoStub
  let dbStub

  beforeEach(() => {
    dbStub = {
      getDatabase: sinon.stub()
    }
    apptDaoStub = {
      fetchAppts: sinon.stub()
    }
    apptService = proxyquire('../../../services/appointment-service', {
      '../db/db': dbStub,
      '../db/dao/appointment-dao': apptDaoStub
    })
  })

  afterEach(() => sinon.restore())

  describe('getAppts', () => {
    it.only('should get appointments', async () => {
      const appts = await apptService.getAppts({})
      dbStub.getDatabase.should.have.been.calledTwice
    })
  })
})
解答

1. 是否需要Stub这两个函数?

必须Stub。单元测试的核心是隔离被测试函数的逻辑,只验证getAppts本身的行为:它是否正确调用了依赖、是否正确返回结果。db.getDatabase()是获取数据库连接,fetchAppts()是实际的数据查询逻辑,这两个都属于外部依赖,Stub后可以避免真实调用数据库,也能精准控制依赖的返回值,确保测试的稳定性和可预测性。

2. 当前测试的问题

你的测试存在几个关键问题:

  • 没有给Stub设置返回值:fetchAppts是异步函数,不设置返回值的话,await会一直等待,导致测试超时或返回undefined;db.getDatabase()也需要返回一个模拟的knex实例,否则fetchAppts会收到无效参数。
  • 断言错误:业务代码里getAppts只调用了一次db.getDatabase(),但你断言它被调用了两次,这必然失败。
  • 未验证返回值:没检查getAppts的返回结果是否符合预期,这是单元测试的核心目标之一。

3. 修正后的测试示例

'use strict'

const sinon = require('sinon')
const chai = require('chai')
const chaiAsPromised = require('chai-as-promised')
const dirtyChai = require('dirty-chai')
const sinonChai = require('sinon-chai')
const proxyquire = require('proxyquire')

const apptsResponse = require('../../data/get-appts-response.json')

chai.use(dirtyChai)
chai.use(sinonChai)
chai.use(chaiAsPromised)
chai.should()

describe('appointment-service.js', () => {
  let apptService
  let apptDaoStub
  let dbStub
  const mockKnex = {} // 模拟knex实例

  beforeEach(() => {
    dbStub = {
      getDatabase: sinon.stub().returns(mockKnex) // 设置返回模拟knex
    }
    apptDaoStub = {
      fetchAppts: sinon.stub().resolves(apptsResponse) // 设置异步返回预设数据
    }
    apptService = proxyquire('../../../services/appointment-service', {
      '../db/db': dbStub,
      '../db/dao/appointment-dao': apptDaoStub
    })
  })

  afterEach(() => sinon.restore())

  describe('getAppts', () => {
    it('should call dependencies correctly and return appointments', async () => {
      const testQueryParams = { status: 'active' }
      const result = await apptService.getAppts(testQueryParams)

      // 验证db.getDatabase被调用一次
      dbStub.getDatabase.should.have.been.calledOnce
      // 验证fetchAppts被传入正确的参数
      apptDaoStub.fetchAppts.should.have.been.calledWithExactly(testQueryParams, mockKnex)
      // 验证返回值符合预期
      result.should.deep.equal(apptsResponse)
    })
  })
})

4. 代码覆盖率评估方式

要确保getAppts的所有代码路径都被覆盖:

  • 正常流程:验证函数能正确调用依赖并返回结果(就是上面的测试场景)。
  • 异常流程:添加测试用例,模拟fetchAppts抛出错误,验证getAppts是否正确传递错误(比如断言函数会抛出对应的异常)。
  • 使用覆盖率工具(如nyc)运行测试,查看getAppts的代码行、分支是否都被覆盖,确保没有遗漏的逻辑。

内容的提问来源于stack exchange,提问作者Moon Shine

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 07:36:17