Javascript 什么';对Nodejs中发出的事件进行单元测试的最佳方法是什么?
我正在编写一系列的摩卡测试,我想测试特定的事件是否被释放。目前,我正在这样做:Javascript 什么';对Nodejs中发出的事件进行单元测试的最佳方法是什么?,javascript,node.js,mocha.js,Javascript,Node.js,Mocha.js,我正在编写一系列的摩卡测试,我想测试特定的事件是否被释放。目前,我正在这样做: it('should emit an some_event', function(done){ myObj.on('some_event',function(){ assert(true); done(); }); }); 但是,如果事件从未发出,则会使测试套件崩溃,而不是使该测试失败 最好的测试方法是什么?如果您可以保证事件应该在一定时间内触发,那么只需设置一个超时
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
});
这可以在的帮助下缩短一点
在这里,我们不仅要检查是否触发了事件,还要检查在超时期间是否只触发了一次事件
Sinon还支持使用调用的和被调用的来检查使用了哪些参数和函数上下文
请注意,如果您希望事件与触发事件的操作同步触发(其间没有异步调用),则可以将超时设置为零。只有在执行需要很长时间才能完成的异步调用时,才需要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
。只要坚持:
this.timeout(<time ms>);
我来晚了,但我正面临着这个问题,想出了另一个解决办法。Bret接受的答案是好的,但我发现在运行完整的mocha测试套件时,它造成了巨大的破坏,抛出了多次调用的错误done()
,我最终放弃了尝试排除故障。Meryl的回答让我找到了自己的解决方案,它也使用了sinon
,但不需要使用超时。通过简单地存根emit()
方法,您可以测试它是否被调用并验证其参数。这假定对象继承自节点的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();
})
})
此方法确保了最短的等待时间,但也确保了套件超时设置的最大机会,并且非常干净
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);
});
通过在done
周围放置一个包装器,这个想法还可以扩展到检查更多细节,例如参数和this
。例如,感谢我能做
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()
}
});
it('日志记录完成后异步发出finish',函数(完成){
const EE=要求(“事件”);
const testEmitter=新EE();
var cb=sinon.spy(已完成);
process.nextTick(()=>testEmitter.emit('finish');
testEmitter.on('finish',cb.bind(null));
process.nextTick(()=>testEmitter.emit('finish');
函数已完成(){
如果(cb.callCount<2)
返回;
expect(cb).to.have.been.calledTwice;
expect(cb).to.have.been.calledOn(null);
expect(cb.to.have.been.calledWithJustice();
完成()
}
});
更好的解决方案代替sinon。计时器使用的是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:(…args)=>resolve(mySpy(…args))包装calback
因此,PROMISnewpromise()。然后()被解析,只有之后才会被调用回调
但一旦调用回调,您就可以测试您对他的期望
优势:
//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
})
- 我们不需要猜测等待事件触发的超时时间(在许多descripes()及其()的情况下),这不取决于计算机的性能
- 而且考试会更快通过
我通过将活动包装在承诺中来实现:
// 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;
}
}
我只想指出,这样做的缺点是,即使事件仅在3毫秒内触发,测试也总是需要1秒(或任何超时时间)。如果您正在运行很多这样的样式测试,那么这可能是一个非常大的缺点,因为这可能会导致它们运行速度慢数百倍。您可以使用更短的超时时间,但会根据事件的性质增加“误报”的风险。所以这真的是一个个案一个个案的决定。布雷特,说得好。我其实没有意识到这一点!你的解决方案显然比这更好。我发布这篇文章主要是为了演示Sinon,因为它被大量用于测试回调。在这种情况下,它可能不太合适。此外,如果多次调用done
,Mocha会自动将测试失败的事件测试为不触发一次。是的,您发布的所有内容都是有效的,值得赞赏的。我只是想指出这一区别。我更新了我的答案,加入了一个事件同步触发的示例,使其更有价值。代码如何“破坏测试套件”?我希望这个特殊的测试会暂停。可能在其他测试代码中存在问题。这很有趣,但我正在尝试测试DOM事件。关于如何工作的想法?我不太精通前端单元测试,但我认为同样的方法也可以工作。这将取决于你事件的具体背景。无论哪种方法等效于emit
,您都可以使用stub。所以对于jQuery,我认为您应该执行类似于var$el=$(“#someElement”)代码>var eventStub=sinon.s
// 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;
}
}