[英]Events in NodeJS, event is being emitted, within setInterval, but not without setInterval
[英]What's the best way to unit test an event being emitted in Nodejs?
我正在編寫一堆 mocha 測試,我想測試發出的特定事件。 目前,我正在這樣做:
it('should emit an some_event', function(done){
myObj.on('some_event',function(){
assert(true);
done();
});
});
但是,如果事件從不發出,它會使測試套件崩潰,而不是使該測試失敗。
測試這個的最好方法是什么?
如果您可以保證事件應在一定時間內觸發,則只需設置超時。
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
});
如果您無法保證事件何時觸發,那么它可能不適合進行單元測試。
編輯9月30日:
我認為我的答案被認為是正確的答案,但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
});
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
});
在這里,我們檢查不僅事件被觸發,而且如果事件在超時期間僅觸發了一次。
興農還支持calledWith
和calledOn
,檢查使用了什么樣的參數和功能方面。
請注意,如果您希望事件與觸發事件的操作同步觸發(中間沒有異步調用),則可以使用零超時。 只有在執行異步調用之間需要很長時間才能完成時,才需要超時1000 ms。 很可能不是這樣的。
實際上,當事件保證與導致它的操作同步觸發時,您可以將代碼簡化為
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風格的功能......
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`
});
在這里晚會,但我正面臨這個問題,並提出了另一種解決方案。 Bret接受的答案很好,但我發現它在運行我的完整mocha測試套件時會造成嚴重破壞,拋出錯誤done() called multiple times
,我最終放棄了嘗試進行故障排除。 Meryl的回答讓我踏上了自己解決方案的道路,該解決方案也使用了sinon
,但不需要使用超時。 通過簡單地對emit()
方法進行存根,您可以測試它是否被調用並驗證其參數。 這假設您的對象繼承自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 - Promises :
//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 new Promise()。then() 僅在被稱為回調之后被解析。
但是一旦調用了回調,你就可以測試你對他的期望了。
優點 :
我通過將事件包裝在Promise中來實現:
// this function is declared outside all my tests, as a helper
const waitForEvent = (asynFunc) => {
return new Promise((resolve, reject) => {
asyncFunc.on('completed', (result) => {
resolve(result);
}
asyncFunc.on('error', (err) => {
reject(err);
}
});
});
it('should do something', async function() {
this.timeout(10000); // in case of long running process
try {
const val = someAsyncFunc();
await waitForEvent(someAsyncFunc);
assert.ok(val)
} catch (e) {
throw e;
}
}
我建議使用once()
來獲得更簡單的解決方案,尤其是如果您喜歡 async/await 風格:
const once = require('events').once
// OR import { once } from 'events'
it('should emit an some_event', async function() {
this.timeout(1000); //timeout with an error if await waits more than 1 sec
p = once(myObj, 'some_event')
// execute some code which should trigger 'some_event' on myObj
await p
});
如果您需要檢查值:
[obj] = await p
assert.equal(obj.a, 'a')
最后,如果您使用的是打字稿,以下幫助程序可能會很方便:
// Wait for event and return first data item
async function onceTyped<T>(event: string): Promise<T> {
return <T>(await once(myObj, event))[0]
}
像這樣使用:
const p = onceTyped<SomeEvent>(myObj, 'some_event')
// execute some code which should trigger 'some_event' on myObj
const someEvent = await p // someEvent has type SomeEvent
assert.equal(someEvent.a, 'a')
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.