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;
    }
}