对Nodej中发出的事件进行单元测试的最佳方法是什么?

时间:2013-05-30 01:05:54

标签: javascript node.js mocha

我正在编写一堆mocha测试,我想测试发出的特定事件。目前,我这样做:

  it('should emit an some_event', function(done){
    myObj.on('some_event',function(){
      assert(true);
      done();
    });
  });

但是,如果事件永远不会发出,它会导致测试套件崩溃而不是失败。

测试这个的最佳方法是什么?

7 个答案:

答案 0 :(得分:33)

如果您可以保证事件应在一定时间内触发,则只需设置超时。

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

如果您无法保证事件何时开启,那么它可能不适合进行单元测试。

答案 1 :(得分:14)

编辑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还支持calledWithcalledOn,以检查使用了哪些参数和函数上下文。

请注意,如果您希望事件与触发事件的操作同步触发(两者之间没有异步调用),那么您可以使用零超时。只有在执行异步调用之间需要很长时间才能完成时,才需要超时1000 ms。很可能不是这样。

实际上,当事件被保证与导致它的操作同步触发时,您可以将代码简化为

  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

答案 2 :(得分:5)

此方法确保最短的等待时间,但是套件超时设置的最大机会非常干净。

  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。例如,感谢this answer我可以做...

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()
    }

});

答案 3 :(得分:3)

坚持:

this.timeout(<time ms>);

在你声明的顶部:

it('should emit an some_event', function(done){
    this.timeout(1000);
    myObj.on('some_event',function(){
      assert(true);
      done();
    });`enter code here`
  });

答案 4 :(得分:2)

这里的聚会晚了,但我正面对这个问题,想出了另一个解决方案。布雷特接受的答案很好,但我发现它在运行我的完整摩卡测试套件时造成了严重破坏,抛出错误done() called multiple times,我最终放弃了尝试进行故障排除。梅丽尔的回答让我在我自己的解决方案的路径上,它也使用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();
    })
})

答案 5 :(得分:0)

更好的解决方案而不是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
})

正如您所看到的,关键是用解决方法包装calback:(... args)=&gt;解决(mySpy(... args))

因此,PROMIS 新Promise()。then()在被称为回调之后解析 ONLY

但是一旦回调被召回,你就可以测试你对他的期望了。

优点

  • 我们不需要猜测超时要等到事件被触发(如果有很多describe()和它的()),不依赖于计算机的性能
  • 并且测试将更快通过

答案 6 :(得分:0)

我通过将事件包装在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;
    }
}