摩卡柴Sinontesting无法访问的承诺/asynchronous/事件发射器
我的设置使用了chai
, sinon
, chai-sinon
, chai-as-promised
, babel
和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的)将通过第二个将失败。