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

Supertest与Sinon:同一测试文件中多次存根同一函数失败问题

问题:Supertest结合Sinon多次存根同一函数失败的原因与解决方法

我无法在同一测试文件中结合Supertest与Sinon对同一函数进行多次存根操作,请问该如何实现?

以下是我当前的代码:

const { app } = require('../../app');
const request = require('supertest');
const sinon = require('sinon');
const { expect } = require('chai');
const testConfig = require('../testConfig/testConfig');
const utils = require('../../helpers/utils');

const token = testConfig.token;
const header = { 'Authorization': `Bearer ${token}` };


const mockOrder = {
   accountNumber: '1234',
   orderSourceId: 1
}

const mockOrders = [mockOrder];



describe('Vendors routes', () => {

   afterEach(() => {
      sinon.restore();
   })


   describe('GET /orders/:accountNumber', () => {

      it('should get an order record for provided Account Number', async () => {
         sinon.stub(utils, 'getOrder').resolves(mockOrders);
         const accountNumber = '1234';

         const route = testConfig.createRoute(`/vendors/orders/${accountNumber}`);
         const res = await request(app)
            .get(route)
            .set(header);

         expect(res.status).to.be.equal(200);
      })


      it('should return 404 if an order is not found', async () => {
         sinon.stub(utils, 'getOrder').resolves(undefined);
         const accountNumber = '1234';

         const route = testConfig.createRoute(`/vendors/orders/${accountNumber}`);
         const res = await request(app)
            .get(route)
            .set(header);

         expect(res.status).to.be.equal(404);
         sinon.restore();

      })
   })
})

运行时出现以下错误:

Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.

第一个测试用例正常通过,但第二个测试用例失败。为什么Sinon无法覆盖已存根的函数?具体原因是什么?


问题原因

  1. 重复恢复存根:你已经在afterEach钩子中配置了自动恢复所有Sinon存根,但第二个测试用例里又手动调用了sinon.restore()。这会导致测试逻辑还在执行时(比如Supertest的请求还没处理完),存根就被提前恢复,路由处理时调用的是原utils.getOrder函数而非存根版本。如果原函数是异步操作且没有被正确模拟,就会导致请求超时。
  2. Node.js模块缓存:如果app在测试文件顶部就被导入,而app内部已经引用了utils模块,那么Node.js会缓存utils的实例。第一次存根修改的是这个缓存实例,但后续测试的存根可能无法作用到app实际使用的utils引用上,导致存根失效。

解决方法

方法一:移除测试用例内的手动恢复

保留afterEach的自动恢复即可,避免提前破坏存根:
修改第二个测试用例,删除末尾的sinon.restore():

it('should return 404 if an order is not found', async () => {
   sinon.stub(utils, 'getOrder').resolves(undefined);
   const accountNumber = '1234';

   const route = testConfig.createRoute(`/vendors/orders/${accountNumber}`);
   const res = await request(app)
      .get(route)
      .set(header);

   expect(res.status).to.be.equal(404);
   // 删掉这里的sinon.restore()
})

方法二:每次测试重新加载app

针对模块缓存问题,可以在每个测试前清除app的模块缓存并重新导入,确保app使用最新的utils存根:

// 移除文件顶部的const { app } = require('../../app');

describe('Vendors routes', () => {
   let app;

   beforeEach(() => {
      // 清除app的模块缓存
      delete require.cache[require.resolve('../../app')];
      // 重新导入app
      app = require('../../app').app;
   })

   afterEach(() => {
      sinon.restore();
   })

   // 测试用例保持不变
})

方法三:针对单个存根进行操作

如果需要更精细的控制,可以只恢复当前测试用例的存根,而非全局恢复:

it('should get an order record for provided Account Number', async () => {
   // 保存存根实例
   const getOrderStub = sinon.stub(utils, 'getOrder').resolves(mockOrders);
   const accountNumber = '1234';

   const route = testConfig.createRoute(`/vendors/orders/${accountNumber}`);
   const res = await request(app)
      .get(route)
      .set(header);

   expect(res.status).to.be.equal(200);
   // 仅恢复这个存根
   getOrderStub.restore();
})

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 02:25:18