私はたくさんのモカテストを書いていますが、特定のイベントが発生することをテストしたいと思います。現在、私はこれをやっています:
it('should emit an some_event', function(done){
myObj.on('some_event',function(){
assert(true);
done();
});
});
ただし、イベントが発行されない場合、その1つのテストに失敗するのではなく、テストスイートがクラッシュします。
これをテストする最良の方法は何ですか?
特定の時間内にイベントが発生することを保証できる場合は、単にタイムアウトを設定します。
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の手法(以下の回答を参照)は、テストが成功したときのほうが速いため、テストスイートの一部としてテストを実行する場合が多いため、単に優れています。
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
});
ここでは、イベントが発生しただけでなく、イベントがタイムアウト期間中に1回だけ発生したかどうかもチェックしています。
Sinonは、calledWith
およびcalledOn
もサポートしており、どの引数と関数コンテキストが使用されたかを確認します。
イベントをトリガーした操作と同期してイベントがトリガーされることを期待している場合(間に非同期呼び出しがない場合)、タイムアウトを0にして実行できることに注意してください。 1000ミリ秒のタイムアウトは、完了までに長い時間がかかる非同期呼び出しを行う場合にのみ必要です。ほとんどの場合そうではありません。
実際、イベントがそれを引き起こした操作と同期して発生することが保証されている場合、コードを次のように単純化できます。
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ステートメントの先頭に:
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();
})
})
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;
}
}
より良い解決策 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
})
ご覧のとおり、キーバックは解決でカルバックをラップすることです:(... args> resolve(mySpy(... args)))==.
したがって、PROMIS new Promise()。then()は解決されます[〜#〜] only [〜#〜]はコールバックと呼ばれます。
しかし、コールバックが呼び出されると、彼に期待することを既にテストできます。
利点: