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

时间:2022-12-29 15:50:03

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?

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

6 个解决方案

#1


30  

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.

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

#2


12  

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:

布雷特科普兰的技术是正确的。您也可以采用不同的方式:

  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 calledWith and calledOn, to check what arguments and function context was used.

Sinon还支持calledWith和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 ms。很可能不是这种情况。

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 done if the event is triggered.

否则,Bret Copeland的技术总是在“成功”情况下更快(希望是常见的情况),因为如果事件被触发,它能够立即调用done。

#3


4  

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 answer I can do...

这个想法也可以扩展到检查更多的细节 - 比如参数和这个 - 通过完成一个包装器。例如,由于这个答案,我可以做...

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

});

#4


3  

Just stick:

坚持:

this.timeout(<time ms>);

at the top of your it statement:

在你的声明的顶部:

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

#5


2  

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 emit method may be different in your case.

迟到这里的派对,但我正面对这个问题,并想出了另一个解决方案。 Bret接受的答案很好,但我发现它在运行我的完整mocha测试套件时会造成严重破坏,抛出错误done()多次调用,我最终放弃了尝试进行故障排除。梅丽尔的回答让我走上了自己解决方案的道路,这个解决方案也使用了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();
    })
})

#6


0  

Better solution instead 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包装calback:(... args)=> resolve(mySpy(... args))。

Thus, PROMIS new Promise().then() is resolved ONLY after 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
  • 我们不需要猜测超时等待事件被触发(在许多describe()及其()的情况下,不依赖于计算机的性能
  • and tests will be faster passing
  • 并且测试将更快通过

#1


30  

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.

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

#2


12  

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:

布雷特科普兰的技术是正确的。您也可以采用不同的方式:

  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 calledWith and calledOn, to check what arguments and function context was used.

Sinon还支持calledWith和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 ms。很可能不是这种情况。

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 done if the event is triggered.

否则,Bret Copeland的技术总是在“成功”情况下更快(希望是常见的情况),因为如果事件被触发,它能够立即调用done。

#3


4  

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 answer I can do...

这个想法也可以扩展到检查更多的细节 - 比如参数和这个 - 通过完成一个包装器。例如,由于这个答案,我可以做...

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

});

#4


3  

Just stick:

坚持:

this.timeout(<time ms>);

at the top of your it statement:

在你的声明的顶部:

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

#5


2  

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 emit method may be different in your case.

迟到这里的派对,但我正面对这个问题,并想出了另一个解决方案。 Bret接受的答案很好,但我发现它在运行我的完整mocha测试套件时会造成严重破坏,抛出错误done()多次调用,我最终放弃了尝试进行故障排除。梅丽尔的回答让我走上了自己解决方案的道路,这个解决方案也使用了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();
    })
})

#6


0  

Better solution instead 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包装calback:(... args)=> resolve(mySpy(... args))。

Thus, PROMIS new Promise().then() is resolved ONLY after 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
  • 我们不需要猜测超时等待事件被触发(在许多describe()及其()的情况下,不依赖于计算机的性能
  • and tests will be faster passing
  • 并且测试将更快通过