it-swarm.com.de

Wie kann ein in Nodejs ausgegebenes Ereignis am besten getestet werden?

Ich schreibe eine Reihe von Mokka-Tests und möchte testen, ob bestimmte Ereignisse ausgelöst werden. Momentan mache ich das:

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

Wenn das Ereignis jedoch nie ausgegeben wird, stürzt die Testsuite ab, anstatt diesen einen Test zu schlagen.

Wie kann man das am besten testen?

24
manalang

Wenn Sie garantieren können, dass das Ereignis innerhalb einer bestimmten Zeit ausgelöst wird, legen Sie einfach ein Timeout fest.

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

Wenn Sie nicht garantieren können, wann das Ereignis ausgelöst wird, ist dies möglicherweise kein guter Kandidat für Komponententests.

33
Bret Copeland

30. September bearbeiten:

Ich sehe meine Antwort als die richtige Antwort akzeptiert, aber die Technik von Bret Copeland (siehe Antwort unten) ist einfach besser, weil sie schneller ist, wenn der Test erfolgreich ist, was meistens der Fall ist, wenn Sie einen Test als Teil einer Testsuite durchführen .


Bret Copelands Technik ist korrekt. Sie können es auch etwas anders machen:

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

Mit Sinon.js kann dies etwas verkürzt werden.

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

Hier wird überprüft, ob das Ereignis nicht nur ausgelöst wurde, sondern auch, ob das Ereignis während der Auszeit nur einmal ausgelöst wurde.

Sinon unterstützt auch calledWith und calledOn, um zu überprüfen, welche Argumente und Funktionskontext verwendet wurden.

Wenn Sie davon ausgehen, dass das Ereignis synchron mit der auslösenden Operation ausgelöst wird (keine asynchronen Aufrufe dazwischen), können Sie ein Timeout von Null verwenden. Ein Timeout von 1000 ms ist nur erforderlich, wenn Sie dazwischen asynchrone Anrufe tätigen, deren Ausführung viel Zeit in Anspruch nimmt. Höchstwahrscheinlich nicht der Fall.

Wenn garantiert wird, dass das Ereignis synchron mit der auslösenden Operation ausgelöst wird, können Sie den Code vereinfachen

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

Ansonsten ist die Technik von Bret Copeland im "Erfolg" -Fall (hoffentlich im Normalfall) immer schneller, da done sofort aufgerufen werden kann, wenn das Ereignis ausgelöst wird.

14
Myrne Stol

Diese Methode gewährleistet die minimale Wartezeit, aber die maximale Chance, die durch das Zeitlimit der Suite festgelegt wurde, und ist ziemlich sauber.

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

Kann auch für CPS-Style-Funktionen verwendet werden ...

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

Die Idee kann auch erweitert werden, um weitere Details wie Argumente und this zu prüfen, indem ein Wrapper um done gelegt wird. Zum Beispiel dank diese Antwort kann ich tun ...

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

Halt einfach:

this.timeout(<time ms>);

an der Spitze Ihrer it-Anweisung:

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

Spät zur Party hier, aber ich stand genau vor diesem Problem und fand eine andere Lösung. Brets akzeptierte Antwort ist eine gute, aber ich fand heraus, dass es Chaos verursachte, als ich meine komplette Mokka-Testsuite durchführte und den Fehler done() called multiple times auslöste, den ich schließlich aufgab, um eine Fehlersuche durchzuführen. Meryls Antwort hat mich auf den Weg zu meiner eigenen Lösung gebracht, die ebenfalls sinon verwendet, jedoch kein Timeout erfordert. Durch einfaches Stubben der emit()-Methode können Sie testen, ob sie aufgerufen wird, und ihre Argumente überprüfen. Dies setzt voraus, dass Ihr Objekt von der EventEmitter-Klasse des Knotens erbt. Der Name der emit-Methode kann in Ihrem Fall unterschiedlich sein.

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

Ich mache es, indem ich das Ereignis in ein Versprechen hülle:

// 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
MFB

Bessere Lösung anstelle von sinon.timers ist es6 - Versprechen :

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

Wie Sie sehen, besteht der Schlüssel darin, Calback mit resol umzuwickeln: (... args) => resol (mySpy (... args)) .

Daher wird PROMIS new Promise (). Then () aufgelöstNURafter wird als Callback bezeichnet.

Aber sobald Callback aufgerufen wurde, können Sie bereits testen, was Sie von ihm erwartet haben.

Die Vorteile :

  • wir brauchen keine Zeitüberschreitung, um zu warten, bis das Ereignis ausgelöst wird (im Fall vieler Beschreibungen () und its ()), unabhängig von der Leistung des Computers
  • und Tests werden schneller passieren
0
meugen