在Nodejs中对事件进行单元测试的最佳方法是什么?
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的技术(见下面的答案)更好,因为它在测试成功时更快,这将是大多数情况下您将测试作为测试套件的一部分运行的情况。
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还支持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();
})
})
更好的解决方案代替sinon。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封装回调:args) => resolve (mySpy(…args) .
因此,PROMIS new Promise().then()被解析只有 after将被调用回调
但是一旦callback被调用,你就可以测试你对他的期望了。
:
- 我们不需要猜测超时等待直到事件被触发(在许多情况下描述()和its()),不依赖于计算机的性能
- 和测试将更快通过
我通过在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')
最后,如果你正在使用typescript,下面的帮助器可能会很方便:
// 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')
- 有条件更新d3.js力图中节点的最佳方法
- 将jQuery.ech()方法转换为本地JavaScript抽象的最佳方法是什么
- 处理浮点错误的最佳方法是什么
- 从数组中删除元素的最佳方法是:javascript/jquery
- 以编程方式填充组合框道场 (1.8) 的最佳方法是什么?
- 列出没有 mysql 的元素的最佳方法是什么
- 在jquery库中重新定义方法的最佳方法
- Javascript,用vars创建对象的最佳方法
- 使用 jQuery 从 HTML 中获取某些值的最佳方法
- 在变量中保存值的最佳方法是在应用程序关闭后使用,然后使用apachecordova在android中重新启动
- 在d3.json中使用d3.csv组合多个csv文件数据输入的最佳方法是什么
- 用jasmine测试JavaScriptUI的最佳方法
- 使用javascript:在没有阻止html标记(<b>、<p>等)的情况下,阻止脚本的最佳方法
- 如果我返回表,检查 Ajax 调用是否为 200 OK的最佳方法是什么
- JavaScript - 创建可链接函数时的最佳方法是什么
- 在上下文菜单项单击上显示侧边栏/弹出窗口的最佳方法是什么
- 在 JavaScript 中获取范围的随机数的最佳方法
- 在对象类上实现 jquery 作用域的最佳方法
- 将同步函数包装到承诺中的最佳方法是什么?
- 使用 MVC 删除 JavaScript 中硬编码字符串 Asp.Net 最佳方法