摩卡柴Sinontesting无法访问的承诺/asynchronous/事件发射器

我的设置使用了chaisinonchai-sinonchai-as-promisedbabel和es6语法。

我有以下(减less)的代码

 // example.js 'use strict'; import EventEmitter from 'events'; class Dispatcher extends EventEmitter { send(x) { doSomethingAsync() // promise is NOT returned .then(() => { this.emit('sent'); }) .catch((err) => { this.emit('error', err); }); } } 

注意:doSomethingAsync的承诺不会返回。 (永远不会)

这是我的(减less的)testing文件

 let dispatcher; let onSent; let onError; beforeEach(() => { dispatcher = new Dispatcher(); onSent = sinon.stub(); onError= sinon.stub(); dispatcher.on('sent', onSent); dispatcher.on('error', onError); }); describe('send', () => { it('should emit "error" on sendFn error instead of "sent"', () => { ... set up state for failure ... dispatcher.send(...); ... What do I do here or how do I wrap the following? ... expect(onSent).not.to.have.been.called; expect(onError).to.have.been.called; }); }); 

我知道如何做到这一点,如果我可以从doSomethingAsync作为send的结果返回承诺,但是这不是这种情况。 我所知道的只是知道“发送”或“错误”事件将最终发出。

我的理想语法如下所示:

 expect(onError).to.eventually.have 

但是,这是行不通的。 我可以得到一个非错误的版本,只需简单地将expect包装在一个新的承诺中。 但我不知道为什么这个工作。

  // This one works for some unknown reason! it('should emit "send" on send success', () => { ... set up state for success ... dispatcher.send(...); return Promise.resolve().then(() => { expect(onSent).to.have.been.called; expect(onError).not.to.have.been.called; }); }); 

如果我能够以暴露内在承诺的方式重构代码,那么这将是微不足道的解决。 在其他情况下,我做了无数次。 然而,我的问题在于如何解决这个确切的模式。 即,如何testing无法访问的承诺或asynchronous代码的副作用,特别是在无法重构代码以揭示承诺的事件发射器周围时。

我至less尝试了以下几点,看是否可以在testing调用期望之前触发所需的发送函数来完成所有内部callback/承诺

  • 将期望包含在承诺中
  • 使用sinon.useFakeTimers控制时间
  • 在超时中包装期望
  • 包装都发送呼叫和期望在各种asynchronous/等待模式

提前致谢。

编辑:

好吧,这是完全荒谬的,但是这里有一个解决scheme可以解决和拒绝承诺:

 it('should behave as expected already!', (done) => { ... set up for failure or success as desired dispatcher.send(); process.nextTick(() => { Promise.resolve().then(() => { ... expectations ... done(); }); }); }); 

我认为这是有效的,因为(我完全猜测在这里!)我假设抛出的错误或拒绝的承诺立即处理当前滴答,而解决的承诺排队在下一个滴答声。 所以… process.nextTick确保我们将这个函数在下一个tick中排队,允许所有捕获/错误完成,并且Promise.resolve确保在任何已经排队的promise运行之后排队。 顺便说一句,你也可以切换顺序或nextTick和promise.resolve(),它工作得很好。

NB如果事件是真正asynchronous发射的(例如,在他们自己的process.nextTick那么你必须有一个3层的嵌套。Promise-nextTick-promise或nextTick-promise-nextTick。

我的话很凌乱!

D.虽然比超时还好:D

由于testing不等待您正在testing解决/拒绝承诺的代码块,断言将不起作用。

你的分析是正确的,但解决的办法是解决承诺的断言。

我已经对你的代码做了一些改变,并使之工作。

我做了doSomethingAsync返回承诺(我认为这是正确的解决scheme)

//代码进行testing

 import EventEmitter from 'events'; const doSomethingAsync = async (x) => { return x; }; class Dispatcher extends EventEmitter { send(x) { return doSomethingAsync(x) .then((res) => { this.emit('sent'); }) .catch((err) => { this.emit('error', err); }); } } export default Dispatcher; 

//testing

选项1:使用await

 import sinon from 'sinon'; import { expect } from 'chai'; import Dispatcher from '../dispatcher'; describe('send', () => { let dispatcher; let onSent; let onError; beforeEach(() => { dispatcher = new Dispatcher(); onSent = sinon.stub(); onError = sinon.stub(); dispatcher.on('sent', onSent); dispatcher.on('error', onError); }); it('Test 1: should emit "error" on sendFn error instead of "sent"', async () => { await dispatcher.send('hello'); expect(onSent.callCount).to.equal(1); expect(onError.callCount).to.equal(0); }); }); 

//结果

纱线运行规格

发送✓应该发送sendFn错误的“错误”,而不是“发送”

1次传球(1s)

✨以4.18s完成。

scheme2:有承诺

在testing中,你可以看到我在dispatcher.send上使用了一个await,或者你也可以使用promise来声明如下

 it('Test 2: should emit "error" on sendFn error instead of "sent"', () => { let error = false; dispatcher.send('hello').then(() => { expect(onSent.callCount).to.equal(1); expect(onError.callCount).to.equal(0); }).catch(() => { error = true; }) expect(error).to.equal(false); }); 

PS: – 如果你不希望返回承诺,那么第一个testing用例(使用await的)将通过第二个将失败。