web-dev-qa-db-fra.com

Quelle est la meilleure façon de tester à l'unité un événement émis dans Nodejs?

J'écris une série de tests de moka et j'aimerais vérifier que des événements particuliers sont émis. Actuellement, je fais ceci:

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

Cependant, si l'événement n'émet jamais, il bloque la suite de tests plutôt que d'échouer ce test.

Quelle est la meilleure façon de tester cela?

24
manalang

Si vous pouvez garantir que l'événement se déclenchera dans un certain délai, définissez simplement un délai d'expiration.

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

Si vous ne pouvez pas garantir le moment où l'événement se déclenchera, il se peut que ce ne soit pas un bon candidat pour les tests unitaires.

33
Bret Copeland

Éditer le 30 septembre:

Je vois que ma réponse est acceptée comme étant la bonne, mais la technique de Bret Copeland (voir la réponse ci-dessous) est tout simplement meilleure car elle est plus rapide car le test réussit, ce qui sera le cas la plupart du temps où vous exécuterez un test dans le cadre d'une suite de tests .


La technique de Bret Copeland est correcte. Vous pouvez aussi le faire un peu différemment:

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

Cela peut être raccourci avec l’aide de 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
  });

Nous vérifions ici que l'événement a été déclenché non seulement, mais également si l'événement a été déclenché une seule fois pendant la période de temporisation.

Sinon supporte également calledWith et calledOn, pour vérifier quels arguments et quel contexte de fonction ont été utilisés.

Notez que si vous vous attendez à ce que l'événement soit déclenché de manière synchrone avec l'opération qui l'a déclenché (aucun appel asynchrone entre les deux), vous pouvez le faire avec un délai d'attente égal à zéro. Un délai d'attente de 1000 ms n'est nécessaire que lorsque vous effectuez des appels asynchrones entre deux qui prennent beaucoup de temps. Probablement pas le cas.

En fait, quand il est garanti que l’événement se déclenche de manière synchrone avec l’opération qui l’a provoqué, vous pouvez simplifier le code.

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

Dans le cas contraire, la technique de Bret Copeland est toujours plus rapide dans le cas de la "réussite" (heureusement le cas le plus courant), car elle est capable d'appeler immédiatement done si l'événement est déclenché.

14
Myrne Stol

Cette méthode garantit le temps d’attente minimal, mais l’opportunité maximale définie par le délai d’expiration de la suite et qui est relativement propre.

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

Peut aussi l'utiliser pour les fonctions de style CPS ...

  it('should call back when done', function(done){
    myAsyncFunction(options, done);
  });

L'idée peut également être étendue pour vérifier plus de détails - tels que les arguments et this - en plaçant un wrapper done. Par exemple, grâce à cette réponse je peux le faire ...

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

});
5
Cool Blue

Il suffit de coller:

this.timeout(<time ms>);

en haut de votre déclaration it:

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

Tard à la fête ici, mais je faisais face exactement à ce problème et proposais une autre solution. La réponse acceptée par Bret est une bonne réponse, mais j’ai constaté qu’elle causait des dégâts lors de l’exécution de ma suite de tests complète de moka, en lançant l’erreur done() called multiple times, que j’ai finalement renoncé à essayer de résoudre. La réponse de Meryl m'a mis sur la voie de ma propre solution, qui utilise également sinon, mais ne nécessite pas l'utilisation d'un délai d'attente. En remplaçant simplement la méthode emit(), vous pouvez tester son appel et vérifier ses arguments. Cela suppose que votre objet hérite de la classe EventEmitter de Node. Le nom de la méthode emit peut être différent dans votre cas.

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();
    })
})
2
Ben

Meilleure solution au lieu de sinon.timers est l'utilisation de 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
})

Comme vous pouvez le constater, l’essentiel est de recouvrir calback de résolution: (... args) => resol (mySpy (... args)) .

Ainsi, PROMIS new Promise (). Then () est résoluSEULaprès sera appelé rappel.

Mais une fois que le rappel a été appelé, vous pouvez déjà tester ce que vous attendiez de lui.

Les avantages :

  • nous n'avons pas besoin de deviner le délai d'attente pour attendre que l'événement soit déclenché (en cas de plusieurs décrit () et its ()), ne dépendant pas de la performance de l'ordinateur
  • et les tests seront plus rapides en passant
0
meugen