使用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; } });
常见踩坑点提醒
- 一定要把Promise返回给Mocha:如果你的测试里有异步操作,要么返回Promise,要么用
done()回调,不然Mocha会误以为测试同步完成,然后傻等超时。 - Stub的对象要准确:确保你Stub的是
sendGridMail.send——也就是你代码里实际调用的那个方法,别Stub错了对象(比如类内部的某个私有方法)。 - 别忽略Promise链的返回:检查你的
sendMail方法有没有漏掉return,如果没返回Promise,Mocha同样会等不到结果。
如果还是超时?
可以先临时增加Mocha的超时时间排查:mocha --timeout 5000,但这只是临时手段。核心要检查:
- 有没有未处理的Promise链?比如某个then/catch里没返回Promise?
- 是不是Stub没生效?可以在测试里加个
console.log(sendMailStub.called)看看有没有被调用。
内容的提问来源于stack exchange,提问作者Shaolin
相关产品推荐
相关产品推荐

