34

I'm writing a bunch of mocha tests and I'd like to test that particular events are emitted. Currently, I'm doing this:

  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?

manalang
  • 805
  • 1
  • 6
  • 10
  • 1
    How is the code "crashing the test suite"? I expect this particular test will just time out. Maybe there are problems in other pieces of the test code. – Myrne Stol May 30 '13 at 12:04

8 Answers8

39

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.

Bret Copeland
  • 22,640
  • 1
  • 25
  • 25
  • There's no need *whatsoever* to use `setTimeout` to set a timeout yourself. Mocha has a timeout facility. `assert(true)` is 100% useless. Mocha won't know it even happened. And if you want to make a test fail, you can just throw any old exception you want. No need to use `assert(false)`. But in this case, an even better way has already pointed out by Ollie Ford: `done(new Error(...))`. – Louis Aug 10 '16 at 11:17
  • @Louis does that look better? – Bret Copeland Aug 11 '16 at 17:13
  • Yes, although now I'm thinking the question is a bad one. I did not actually take a close look at it earlier but, as the comment on the question indicates, the test the OP showed should just time out rather than "crash". The answer here is not functionally different from what the OP is doing. You have `this.timeout(1000)` but Mocha has a default timeout of 2000 so we are never running without a timeout. All the answers on this question are either doing what the OP is already doing or they include unnecessary contortions to try to fix a non-existent problem. – Louis Aug 11 '16 at 17:47
  • 1
    @Louis that's a good point, though it's probably not worth worrying about the OP's intent since the question is three years old. – Bret Copeland Aug 11 '16 at 17:53
17

Edit Sept 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'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.

  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.

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.

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.

Myrne Stol
  • 11,222
  • 4
  • 40
  • 48
  • I would just note that the disadvantage of doing it this way is that the test will always take one second (or whatever the timeout is), even if the event were to fire in just 3 milliseconds. That could be a pretty big disadvantage if you're running a lot of these style tests since it could cause them to run hundreds of times slower. You could use a shorter timeout, but you increase your risk of "false negatives" depending on the nature of the events. So it's really a case-by-case decision. – Bret Copeland May 30 '13 at 11:47
  • Bret, good point. I actually had not realized this difference! Your solution is clearly superior then. I mainly posted this to demonstrate Sinon, because that's used a lot for testing callbacks. In this case, it may be less appropriate. Also, testing the event not firing more than once is covered by Mocha automatically failing a test if `done` is called multiple times. – Myrne Stol May 30 '13 at 11:51
  • Yeah, everything you posted is valid and appreciated. I just wanted to point out that difference. – Bret Copeland May 30 '13 at 11:56
  • I updated my answer to include an example for when event is fired synchronously synchronous, to make it somewhat more worthwhile. – Myrne Stol May 30 '13 at 12:00
7

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...

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

});
Community
  • 1
  • 1
Cool Blue
  • 6,438
  • 6
  • 29
  • 68
  • Yes, this is exactly how it should be done. There's no reason to use `setTimeout`. – Louis Aug 10 '16 at 11:09
  • I'm not seeing the point of doing `process.nextTick`. If you move `testEmitter.on` before the first `process.nextTick` everything will work just fine by calling `testEmitter.emit('finish')` directly. – Louis Aug 10 '16 at 17:04
  • @Louis it's just to simulate the actual situation. I am testing an object that emits an event after completing a task. Sometimes the task is synchronous and sometimes not, but the object must always act asynchronously and that is one of the characteristics I'm testing. If it returns synchronously, the above test will fail. I could also do it by subscribing to the event after calling the object but I did it this way to test that, in general, it doesn't matter if the call is before or after the subscription. – Cool Blue Aug 10 '16 at 23:57
  • Thank you for taking time to write the way to capture callbacks. – Gary Jun 12 '17 at 08:01
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`
  });
JasonMArcher
  • 14,195
  • 22
  • 56
  • 52
Pete
  • 39
  • 1
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.

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();
    })
})
Ben
  • 571
  • 2
  • 4
  • 15
  • it's interesting but I'm trying to test DOM events. thoughts on how that might work? – ekkis Nov 27 '15 at 05:11
  • 1
    I'm not as versed in front-end unit tests, but I think the same method would work. It's going to depend on the specific context of your events. You'd just stub whichever method is the equivalent of `emit`. So for jQuery, I think you'd do something like `var $el = $("#someElement");` `var eventStub = sinon.stub($el, 'trigger');` This is totally speculative and untested, but hopefully helps you toward a working solution. – Ben Nov 28 '15 at 17:05
0

Better solution instead of sinon.timers is use of 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)).

Thus, PROMIS new Promise().then() is resolved ONLY after will be called callback.

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
  • and tests will be faster passing
meugen
  • 1
  • 1
  • In some cases, yes, promises are the way to go but here there's no benefit whatsoever to using promises. Doing so just adds unnecessary machinery to the solution. – Louis Nov 07 '16 at 11:48
0

I do it by wrapping the event in a 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;
    }
}
MFB
  • 19,017
  • 27
  • 72
  • 118
0

I suggest using once() for an even simpler solution, especially if you like async/await style:

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

If you need to check values:

[obj] = await p
assert.equal(obj.a, 'a')

Finally, if you're using typescript, the following helper might be handy:

// Wait for event and return first data item
async function onceTyped<T>(event: string): Promise<T> {
    return <T>(await once(myObj, event))[0]
}

Use like this:

 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')
mrtnlrsn
  • 1,105
  • 11
  • 19