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

使用Sinon Stub第三方SendGrid SDK遇Promise超时问题(Node.js、Mocha)

解决Mocha测试中Sinon Stub SendGrid SDK的Promise超时问题

我之前也碰到过类似的坑——明明测试逻辑没复杂到超时,结果Mocha一直报2000ms超时,查了半天才发现是Promise没正确resolve/reject导致的。结合你提供的代码片段,给你梳理下问题根源和解决办法:

先明确问题场景

首先我补全下你的sendGridClient类(应该有个发送邮件的核心方法吧),方便后续说明:

import sendGridMail from '@sendgrid/mail';
import Promise from 'bluebird';

export default class SendGridClient {
  constructor(apiKey) {
    this.apiKey = apiKey;
    sendGridMail.setApiKey(this.apiKey);
  }

  sendMail(mailOptions) {
    return Promise.resolve(sendGridMail.send(mailOptions))
      .then((response) => {
        // 自定义成功处理逻辑
        return response;
      })
      .catch((error) => {
        // 自定义错误处理逻辑
        throw error;
      });
  }
}

问题出在:你的代码用了Bluebird Promise,而SendGrid SDK本身用的是原生Promise。当你用Sinon Stub sendGridMail.send时,如果没返回一个状态明确(resolved/rejected)的Promise,Bluebird的Promise链会一直处于pending状态,Mocha等不到结果就会触发超时。

正确的Sinon Stub姿势

方案一:Stub返回Bluebird Promise(和代码保持一致)

既然你的代码依赖Bluebird,那Stub的时候直接返回Bluebird的Promise,避免Promise类型不一致导致的隐性问题:

const sinon = require('sinon');
const chai = require('chai');
const expect = chai.expect;
const SendGridClient = require('../path/to/sendGridClient').default;
const sendGridMail = require('@sendgrid/mail');
const Promise = require('bluebird');

describe('SendGridClient', () => {
  let sendMailStub;
  let client;

  beforeEach(() => {
    // 初始化Stub,拦截SendGrid的send方法
    sendMailStub = sinon.stub(sendGridMail, 'send');
    client = new SendGridClient('test-api-key');
  });

  afterEach(() => {
    // 测试结束后恢复原方法,避免影响其他测试
    sendMailStub.restore();
  });

  it('should send email successfully', () => {
    // 让Stub返回一个已resolve的Bluebird Promise
    sendMailStub.returns(Promise.resolve([{ statusCode: 202 }]));

    // 关键:把Promise返回给Mocha,Mocha会自动等待它resolve
    return client.sendMail({ 
      to: 'test@example.com', 
      from: 'sender@example.com', 
      subject: 'Test', 
      text: 'Hello' 
    })
      .then((response) => {
        expect(response).to.deep.equal([{ statusCode: 202 }]);
        expect(sendMailStub.calledOnce).to.be.true;
      });
  });

  it('should handle email send failure', () => {
    const testError = new Error('Send failed');
    // 让Stub返回一个已reject的Bluebird Promise
    sendMailStub.returns(Promise.reject(testError));

    // 同样返回Promise给Mocha,在catch里断言错误
    return client.sendMail({ 
      to: 'test@example.com', 
      from: 'sender@example.com', 
      subject: 'Test', 
      text: 'Hello' 
    })
      .catch((error) => {
        expect(error).to.equal(testError);
        expect(sendMailStub.calledOnce).to.be.true;
      });
  });
});

方案二:用async/await简化代码(更易读)

Mocha完全支持async/await语法,这样写测试逻辑更直观,同时也能确保Promise被正确处理:

it('should send email successfully', async () => {
  sendMailStub.returns(Promise.resolve([{ statusCode: 202 }]));

  const response = await client.sendMail({ 
    to: 'test@example.com', 
    from: 'sender@example.com', 
    subject: 'Test', 
    text: 'Hello' 
  });
  
  expect(response).to.deep.equal([{ statusCode: 202 }]);
  expect(sendMailStub.calledOnce).to.be.true;
});

it('should handle email send failure', async () => {
  const testError = new Error('Send failed');
  sendMailStub.returns(Promise.reject(testError));

  try {
    await client.sendMail({ 
      to: 'test@example.com', 
      from: 'sender@example.com', 
      subject: 'Test', 
      text: 'Hello' 
    });
    // 如果走到这里说明没抛出错误,直接断言失败
    expect.fail('Expected error to be thrown');
  } catch (error) {
    expect(error).to.equal(testError);
    expect(sendMailStub.calledOnce).to.be.true;
  }
});

常见踩坑点提醒

  1. 一定要把Promise返回给Mocha:如果你的测试里有异步操作,要么返回Promise,要么用done()回调,不然Mocha会误以为测试同步完成,然后傻等超时。
  2. Stub的对象要准确:确保你Stub的是sendGridMail.send——也就是你代码里实际调用的那个方法,别Stub错了对象(比如类内部的某个私有方法)。
  3. 别忽略Promise链的返回:检查你的sendMail方法有没有漏掉return,如果没返回Promise,Mocha同样会等不到结果。

如果还是超时?

可以先临时增加Mocha的超时时间排查:mocha --timeout 5000,但这只是临时手段。核心要检查:

  • 有没有未处理的Promise链?比如某个then/catch里没返回Promise?
  • 是不是Stub没生效?可以在测试里加个console.log(sendMailStub.called)看看有没有被调用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 11:57:57