What's the best way to unit test an event being emitted in Nodejs?

后端 未结 7 664
孤独总比滥情好
孤独总比滥情好 2021-02-05 02:12

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\', fun         


        
相关标签:
7条回答
  • 2021-02-05 02:21

    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
    0 讨论(0)
  • 2021-02-05 02:21

    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;
        }
    }
    
    0 讨论(0)
  • 2021-02-05 02:34

    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()
        }
    
    });
    
    0 讨论(0)
  • 2021-02-05 02:36

    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.

    0 讨论(0)
  • 2021-02-05 02:36

    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.

    0 讨论(0)
  • 2021-02-05 02:48

    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`
      });
    
    0 讨论(0)
提交回复
热议问题