对 Nodejs 中发出的事件进行单元测试的最佳方法是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16826352/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What's the best way to unit test an event being emitted in Nodejs?
提问by manalang
I'm writing a bunch of mocha tests and I'd like to test that particular events are emitted. Currently, I'm doing this:
我正在编写一堆 mocha 测试,我想测试发出的特定事件。目前,我正在这样做:
it('should emit an some_event', function(done){
myObj.on('some_event',function(){
assert(true);
done();
});
});
However, if the event never emits, it crashes the test suite rather than failing that one test.
但是,如果事件从不发出,它会使测试套件崩溃,而不是使该测试失败。
What's the best way to test this?
测试这个的最好方法是什么?
回答by Bret Copeland
If you can guarantee that the event should fire within a certain amount of time, then simply set a timeout.
如果您可以保证事件应该在一定时间内触发,那么只需设置超时即可。
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
});
If you can't guarantee when the event will fire, then it might not be a good candidate for unit testing.
如果您不能保证事件何时触发,那么它可能不适合进行单元测试。
回答by Myrne Stol
Edit Sept 30:
9 月 30 日编辑:
I see my answer is accepted as the right answer, but Bret Copeland's technique (see answer below) is simply better because it's faster for when the test is successful, which will be the case most times you run a test as part of a test suite.
我看到我的答案被接受为正确答案,但 Bret Copeland 的技术(请参阅下面的答案)更好,因为它在测试成功时更快,大多数情况下您将测试作为测试套件的一部分运行.
Bret Copeland's technique is correct. You can also do it a bit differently:
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
});
This can be made a little shorter with help of Sinon.js.
这可以在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
});
Here we're checking that not only has the event fired, but also if if event has fired only once during the time-out period.
在这里,我们不仅要检查事件是否被触发,还要检查事件是否在超时期间只触发了一次。
Sinon also supports calledWithand calledOn, to check what arguments and function context was used.
Sinon 还支持calledWithand calledOn, 以检查使用了哪些参数和函数上下文。
Note that if you expect the event to be triggered synchronously with the operation that triggered the event (no async calls in between) then you can do with a timeout of zero. A timeout of 1000 ms is only necessary when you do async calls in between which take a long time to complete. Most likely not the case.
请注意,如果您希望事件与触发事件的操作同步触发(两者之间没有异步调用),那么您可以将超时设置为零。1000 毫秒的超时仅在您执行需要很长时间才能完成的异步调用时才需要。很可能不是这样。
Actually, when the event is guaranteed to fire synchronously with the operation that caused it, you could simplify the code to
实际上,当保证事件与导致它的操作同步触发时,您可以将代码简化为
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');
});
Otherwise, Bret Copeland's technique is always faster in the "success" case (hopefully the common case), since it's able to immediately call doneif the event is triggered.
否则,Bret Copeland 的技术在“成功”情况下(希望是常见情况)总是更快,因为done如果事件被触发,它能够立即调用。
回答by Cool Blue
This method ensures the minimum time to wait but the maximum opportunity as set by the suite timeout and is quite clean.
此方法可确保等待的时间最短,但机会最大,由套件超时设置,并且非常干净。
it('should emit an some_event', function(done){
myObj.on('some_event', done);
});
Can also use it for CPS style functions...
也可以将它用于 CPS 风格的功能......
it('should call back when done', function(done){
myAsyncFunction(options, done);
});
The idea can also be extended to check more details - such as arguments and this- by putting a wrapper arround done. For example, thanks to this answerI can do...
这个想法也可以扩展到检查更多细节 - 例如参数和this- 通过放置一个包装器 arround 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()
}
});
回答by Pete
Just stick:
只是坚持:
this.timeout(<time ms>);
at the top of your it statement:
在您的 it 声明的顶部:
it('should emit an some_event', function(done){
this.timeout(1000);
myObj.on('some_event',function(){
assert(true);
done();
});`enter code here`
});
回答by Ben
Late to the party here, but I was facing exactly this problem and came up with another solution. Bret's accepted answer is a good one, but I found that it wreaked havoc when running my full mocha test suite, throwing the error done() called multiple times, which I ultimately gave up trying to troubleshoot. Meryl's answer set me on the path to my own solution, which also uses sinon, but does not require the use of a timeout. By simply stubbing the emit()method, you can test that it is called and verify its arguments. This assumes that your object inherits from Node's EventEmitter class. The name of the emitmethod may be different in your case.
迟到了,但我正面临这个问题,并想出了另一个解决方案。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();
})
})
回答by meugen
Better solutioninstead of sinon.timers is use of es6 - Promises:
更好的解决方案而不是 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
})
As you can see, the key is to wrap calback with resolve: (... args) => resolve (mySpy (... args)).
如您所见,关键是使用 resolve: (... args) => resolve (mySpy (... args))来包装回调。
Thus, PROMIS new Promise().then()is resolved ONLYafter will be called callback.
因此,PROMIS new Promise().then()只有在被调用回调之后才会被解析。
But once callback was called, you can already test, what you expected of him.
但是一旦回调被调用,你就可以测试你对他的期望。
The advantages:
优点:
- we dont need to guess timeout to wait until event is fired (in case of many describes() and its()), not depending on perfomance of computer
- and tests will be faster passing
- 我们不需要猜测超时等待事件被触发(在许多描述()及其()的情况下),不取决于计算机的性能
- 并且测试会更快通过
回答by MFB
I do it by wrapping the event in a Promise:
我通过将事件包装在 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;
}
}

