Node.js Mocha测试Promise结合Stub遇异常:超时与Stub未重置
解决Mocha测试Promise的超时与Stub污染问题
一、处理超时错误
你碰到的Timeout of 2000ms exceeded错误,核心是Mocha没法判断你的异步测试何时完成。针对Promise类型的异步代码,有几种实用的解决方式:
直接返回Promise给Mocha
测试用例直接返回getMarketData的Promise,Mocha会自动等待它resolve或reject,不用手动调用done():it('should fetch valid market data', () => { const handler = new Handler(); // 返回Promise,Mocha会等待它完成后再结束测试 return handler.getMarketData('BTC').then(data => { // 你的断言逻辑 assert.ok(data); assert.strictEqual(data.symbol, 'BTC'); }); });用async/await简化代码
把测试函数改成async函数,用await等待Promise完成,代码更直观,Mocha同样会自动等待异步操作结束:it('should fetch valid market data', async () => { const handler = new Handler(); const data = await handler.getMarketData('BTC'); // 断言逻辑 assert.ok(data); assert.strictEqual(data.symbol, 'BTC'); });按需延长超时时间
如果你的接口请求确实需要超过2000ms,可以在测试用例或describe块里设置更长的超时(注意要用普通函数,别用箭头函数,否则this绑定会出错):describe('Handler', function() { this.timeout(5000); // 把整个describe块的超时设为5秒 it('should fetch valid market data', async () => { // 测试逻辑 }); });
二、解决Stub未恢复导致的测试污染问题
测试之间的隔离性是关键,requestStub没被清除或恢复,会让后续测试继承之前的stub行为。这里分两种常见场景给出解决方案:
场景1:用Sinon.js创建Stub
如果用Sinon来stubrequest模块,一定要在每个测试后恢复stub的原始行为,最好用afterEach钩子自动执行:
const sinon = require('sinon'); const request = require('request'); const Handler = require('./handler'); describe('Handler', () => { let requestStub; beforeEach(() => { // 每个测试前创建全新的stub requestStub = sinon.stub(request, 'get') .resolves({ body: JSON.stringify({ symbol: 'BTC', price: 30000 }) }); }); afterEach(() => { // 每个测试后恢复request.get的原始实现 requestStub.restore(); }); it('should return correct BTC market data', async () => { const handler = new Handler(); const data = await handler.getMarketData('BTC'); assert.strictEqual(data.price, 30000); }); it('should handle ETH correctly without interference', async () => { // 这里可以重新设置stub返回值,和上一个测试互不影响 requestStub.resolves({ body: JSON.stringify({ symbol: 'ETH', price: 2000 }) }); const handler = new Handler(); const data = await handler.getMarketData('ETH'); assert.strictEqual(data.price, 2000); }); });
场景2:用Proxyquire注入Stub
如果用Proxyquire替换依赖的request模块,要确保每个测试都重新创建新的Handler实例和Stub,彻底避免状态共享:
const proxyquire = require('proxyquire'); const sinon = require('sinon'); describe('Handler', () => { it('should return BTC market data', async () => { // 为当前测试创建独立的stub const requestStub = { get: sinon.stub().resolves({ body: JSON.stringify({ symbol: 'BTC', price: 30000 }) }) }; // 注入stub并加载Handler const Handler = proxyquire('./handler', { request: requestStub }); const handler = new Handler(); const data = await handler.getMarketData('BTC'); assert.strictEqual(data.price, 30000); }); it('should return ETH market data without cross-test pollution', async () => { // 重新创建全新的stub和Handler const requestStub = { get: sinon.stub().resolves({ body: JSON.stringify({ symbol: 'ETH', price: 2000 }) }) }; const Handler = proxyquire('./handler', { request: requestStub }); const handler = new Handler(); const data = await handler.getMarketData('ETH'); assert.strictEqual(data.price, 2000); }); });
核心原则就是:每个测试都要有独立的依赖和实例,彻底避免测试之间的状态共享,这样就能解决stub污染的问题了。
内容的提问来源于stack exchange,提问作者quartaela
相关产品推荐
相关产品推荐

