unit testing在Nodejs中发出的事件的最好方法是什么?

我正在写一堆摩卡testing,我想testing一下发生的特定事件。 目前,我正在这样做:

it('should emit an some_event', function(done){ myObj.on('some_event',function(){ assert(true); done(); }); }); 

但是,如果这个事件从不发生,它会使testing套件崩溃,而不是失败那个testing。

什么是testing这个最好的方法?

如果可以保证事件应该在一定时间内触发,那么只需设置一个超时时间。

 it('should emit an some_event', function(done){ this.timeout(1000); //timeout with an error if done() isn't called within one second myObj.on('some_event',function(){ // perform any other assertions you want here done(); }); // execute some code which should trigger 'some_event' on myObj }); 

如果你不能保证事件何时会触发,那么它可能不是一个很好的候选unit testing。

编辑9月30日:

我看到我的答案被接受为正确的答案,但是Bret Copeland的技巧(见下面的答案)更好,因为testing成功的时候速度会更快,大多数情况下,您将testing作为testing套件的一部分。


Bret Copeland的技术是正确的。 你也可以做一些不同的事情:

  it('should emit an some_event', function(done){ var eventFired = false setTimeout(function () { assert(eventFired, 'Event did not fire in 1000 ms.'); done(); }, 1000); //timeout with an error in one second myObj.on('some_event',function(){ eventFired = true }); // do something that should trigger the event }); 

在Sinon.js的帮助下,这可以缩短一点 。

  it('should emit an some_event', function(done){ var eventSpy = sinon.spy() setTimeout(function () { assert(eventSpy.called, 'Event did not fire in 1000ms.'); assert(eventSpy.calledOnce, 'Event fired more than once'); done(); }, 1000); //timeout with an error in one second myObj.on('some_event',eventSpy); // do something that should trigger the event }); 

在这里,我们检查的不仅是事件被触发,而且如果事件在超时期间只触发一次。

Sinon还支持calledOncalledOn ,以检查使用了什么参数和函数上下文。

请注意,如果您希望事件与触发事件的操作同步触发(两者之间没有asynchronous调用),则可以使用超时为零的操作。 1000 ms的超时只有在需要很长时间才能完成的asynchronous调用时才需要。 最有可能不是这种情况。

实际上,当事件被保证与引发它的操作同步触发时,你可以简化代码

  it('should emit an some_event', function() { eventSpy = sinon.spy() myObj.on('some_event',eventSpy); // do something that should trigger the event assert(eventSpy.called, 'Event did not fire.'); assert(eventSpy.calledOnce, 'Event fired more than once'); }); 

否则,Bret Copeland的技术在“成功”的情况下总是比较快(希望是常见的情况),因为如果事件被触发,它可以立即调用done

这种方法确保了最短的等待时间,但由套件超时设置的最大机会是相当干净的。

  it('should emit an some_event', function(done){ myObj.on('some_event', done); }); 

也可以将其用于CPS风格的function…

  it('should call back when done', function(done){ myAsyncFunction(options, done); }); 

这个想法也可以扩展到检查更多的细节 – 如参数和this – 通过把一个包装done 。 例如,多亏了这个答案,我可以做…

 it('asynchronously emits finish after logging is complete', function(done){ const EE = require('events'); const testEmitter = new EE(); var cb = sinon.spy(completed); process.nextTick(() => testEmitter.emit('finish')); testEmitter.on('finish', cb.bind(null)); process.nextTick(() => testEmitter.emit('finish')); function completed() { if(cb.callCount < 2) return; expect(cb).to.have.been.calledTwice; expect(cb).to.have.been.calledOn(null); expect(cb).to.have.been.calledWithExactly(); done() } }); 

坚持:

 this.timeout(<time ms>); 

在你的声明的顶部:

 it('should emit an some_event', function(done){ this.timeout(1000); myObj.on('some_event',function(){ assert(true); done(); });`enter code here` }); 

在这里参加晚会,但我正面对这个问题,并提出了另一个解决scheme。 布雷特接受的答案是好的,但是我发现,在运行我的全套摩卡testing套件时,它发生了严重的破坏,抛出错误done() called multiple times ,最终我放弃了尝试排除故障。 梅丽尔的回答使我走上了我自己的解决scheme,也使用了sinon ,但不需要使用超时。 通过简单地存留emit()方法,可以testing它是否被调用并validation其参数。 这假定你的对象inheritance自Node的EventEmitter类。 emit方法的名称可能会有所不同。

 var sinon = require('sinon'); // ... describe("#someMethod", function(){ it("should emit `some_event`", function(done){ var myObj = new MyObj({/* some params */}) // This assumes your object inherits from Node's EventEmitter // The name of your `emit` method may be different, eg `trigger` var eventStub = sinon.stub(myObj, 'emit') myObj.someMethod(); eventStub.calledWith("some_event").should.eql(true); eventStub.restore(); done(); }) }) 

更好的解决办法,而不是sinon.timers是使用es6 – 承诺

 //Simple EventEmitter let emitEvent = ( eventType, callback ) => callback( eventType ) //Test case it('Try to test ASYNC event emitter', () => { let mySpy = sinon.spy() //callback return expect( new Promise( resolve => { //event happends in 300 ms setTimeout( () => { emitEvent('Event is fired!', (...args) => resolve( mySpy(...args) )) }, 300 ) //call wrapped callback } ) .then( () => mySpy.args )).to.eventually.be.deep.equal([['Event is fired!']]) //ok }) 

正如你所看到的,关键是使用resolve来包装calback: (… args)=> resolve(mySpy(… args))

因此,PROMIS 新的Promise()。then()将在被调用callback后parsing。

但是一旦callback被调用,你就可以testing,你对他的期望是什么。

优点

  • 我们不需要猜测超时等待,直到事件被触发(在许多描述()和它的())的情况下,不依赖于计算机的性能
  • 而且testing会更快通过