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

如何使用Mocha与Sinon模拟类async/await方法并编写路由测试用例

解决Detector类的Mocha+Sinon测试问题

我明白你现在的困扰:明明想用Sinon模拟Detector类的方法,结果还是触发了原方法,而且不知道怎么处理async/await的模拟。下面我会结合实际代码示例,一步步教你写好路由的测试用例。

首先,先明确几个核心问题的原因:

  • 你之前触发原方法,大概率是因为没有正确替换路由中依赖的Detector类,或者是因为getcontent是箭头函数,直接stub原型方法无效(箭头函数绑定在实例上,不是原型)。
  • 模拟async/await其实很简单,只要让Sinon的stub返回一个Promise即可,Sinon专门提供了resolves和rejects方法来处理这种场景。

先补全你的代码上下文

假设你的Detector类完整代码是这样的(补全了语法错误):

// Detector.js
export default class Detector {
  constructor() {}
  setDetectorStrategy(detector) {
    this.detector = detector;
  }
  getcontent = async(payload) => {
    console.log('kkkkkkkkkkkkkkkkkkk...');
    // 这里是实际的异步逻辑,比如调用API
    return '真实内容';
  }
}

路由文件(比如router.js)大概是这样:

// router.js
import express from 'express';
import Detector from './Detector';

const router = express.Router();
const detectorInstance = new Detector();

// 假设这是你要测试的路由
router.post('/get-content', async (req, res) => {
  try {
    const content = await detectorInstance.getcontent(req.body);
    res.status(200).json({ success: true, content });
  } catch (err) {
    res.status(500).json({ success: false, error: err.message });
  }
});

export default router;

编写测试用例的步骤

我们会用到mocha(测试框架)、sinon(模拟工具)、proxyquire(替换模块依赖)、supertest(测试HTTP路由)、chai(断言库),先确保这些依赖都安装了。

1. 测试文件结构(detect-router.test.js)

const { expect } = require('chai');
const sinon = require('sinon');
const proxyquire = require('proxyquire');
const supertest = require('supertest');
const express = require('express');

describe('Detector 路由测试', () => {
  let app;
  let detectorStub; // Detector类的stub
  let detectorInstanceStub; // Detector实例的stub

  // 每个测试用例前初始化stub和app
  beforeEach(() => {
    // 1. 创建实例的方法stub:因为getcontent是箭头函数,必须直接stub实例上的方法
    detectorInstanceStub = {
      getcontent: sinon.stub(),
      setDetectorStrategy: sinon.stub()
    };

    // 2. 创建Detector类的stub:调用new Detector()时返回我们的实例stub
    detectorStub = sinon.stub().returns(detectorInstanceStub);

    // 3. 用proxyquire加载路由,替换掉真实的Detector依赖
    const router = proxyquire('./router', {
      './Detector': detectorStub
    }).default;

    // 4. 创建express app并挂载路由
    app = express();
    app.use(express.json()); // 解析JSON请求体
    app.use(router);
  });

  // 每个测试用例后清除stub的调用记录
  afterEach(() => {
    sinon.restore();
  });

  // 测试成功场景:getcontent返回模拟内容
  it('应该返回模拟的内容当getcontent成功时', async () => {
    // 模拟async方法的成功返回值
    const mockContent = '我是模拟的内容';
    detectorInstanceStub.getcontent.resolves(mockContent);

    // 发送POST请求到路由
    const response = await supertest(app)
      .post('/get-content')
      .send({ key: 'test' });

    // 断言响应结果
    expect(response.status).to.equal(200);
    expect(response.body.success).to.be.true;
    expect(response.body.content).to.equal(mockContent);

    // 断言getcontent被正确调用,参数是请求体
    expect(detectorInstanceStub.getcontent.calledOnceWith({ key: 'test' })).to.be.true;
  });

  // 测试失败场景:getcontent抛出错误
  it('应该返回错误当getcontent失败时', async () => {
    // 模拟async方法抛出错误
    const mockError = new Error('获取内容失败');
    detectorInstanceStub.getcontent.rejects(mockError);

    // 发送POST请求
    const response = await supertest(app)
      .post('/get-content')
      .send({ key: 'test' });

    // 断言响应结果
    expect(response.status).to.equal(500);
    expect(response.body.success).to.be.false;
    expect(response.body.error).to.equal(mockError.message);

    // 断言getcontent被调用
    expect(detectorInstanceStub.getcontent.calledOnce).to.be.true;
  });

  // 测试setDetectorStrategy是否被调用(如果你的路由中有调用这个方法的逻辑)
  it('应该正确调用setDetectorStrategy方法', async () => {
    // 假设路由中初始化时调用了setDetectorStrategy
    expect(detectorStub.calledOnce).to.be.true; // 验证Detector类被实例化
    // 如果你的路由中有 detectorInstance.setDetectorStrategy(xxx),可以加这个断言:
    // expect(detectorInstanceStub.setDetectorStrategy.calledOnce).to.be.true;
  });
});

关键知识点解释

  1. 为什么用proxyquire?
    因为路由文件中直接import Detector并创建了实例,如果不替换这个依赖,测试时会一直用真实的Detector类,所以用proxyquire可以在加载路由时,把真实的Detector换成我们的stub类。

  2. 如何模拟async/await函数?
    async函数本质是返回Promise,所以用sinon.stub().resolves(模拟值)来模拟成功的Promise,用rejects(错误对象)来模拟失败的Promise,这样路由中的await就能拿到我们模拟的结果或者捕获错误。

  3. 箭头函数的stub注意事项
    你的getcontent是箭头函数,它是定义在实例上的属性,而不是原型方法,所以不能通过sinon.stub(Detector.prototype, 'getcontent')来模拟,必须直接stub实例上的方法,这也是我们用proxyquire返回实例stub的原因。

运行测试

在package.json中添加测试脚本:

{
  "scripts": {
    "test": "mocha detect-router.test.js"
  }
}

然后执行npm test就能看到测试结果了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 11:29:12