Подтвердить что ты не робот

Каков наилучший способ для unit test события, испускаемого в Nodejs?

Я пишу кучу мокко-тестов, и я бы хотел проверить, что определенные события испускаются. В настоящее время я делаю это:

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

Однако, если событие никогда не испускает, оно разбивает тестовый пакет, а не на то, что один тест.

Какой лучший способ проверить это?

4b9b3361

Ответ 1

Если вы можете гарантировать, что событие должно срабатывать в течение определенного промежутка времени, просто установите тайм-аут.

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

Если вы не можете гарантировать, что событие будет срабатывать, возможно, это не будет хорошим кандидатом для модульного тестирования.

Ответ 2

Редактировать 30 сентября:

Я вижу, что мой ответ принят в качестве правильного ответа, но технику Брета Коупленда (см. ответ ниже) просто лучше, потому что это быстрее, когда тест прошел успешно, что будет иметь место чаще всего, когда вы запускаете тест как часть тестовый пакет.


Техника Брет Коупленд правильна. Вы также можете сделать это несколько иначе:

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

Это можно сделать немного короче с помощью 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
  });

Здесь мы проверяем, что не только событие было запущено, но также, если событие было запущено только один раз в течение тайм-аута.

Sinon также поддерживает calledWith и calledOn, чтобы проверить, какие аргументы и контекст функции были использованы.

Обратите внимание, что если вы ожидаете, что событие будет запущено синхронно с операцией, вызвавшей событие (без асинхронных вызовов между ними), вы можете сделать это с нулевым временем. Тайм-аут 1000 мс необходим только тогда, когда вы выполняете асинхронные вызовы, между которыми требуется много времени. Скорее всего, не так.

На самом деле, когда событие гарантированно срабатывает синхронно с вызванной им операцией, вы можете упростить код

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

В противном случае техника Брет Коупленд всегда быстрее в случае "успеха" (надеюсь, общий случай), так как она может немедленно вызвать done, если событие вызвано.

Ответ 3

Этот метод обеспечивает минимальное время ожидания, но максимальную возможность, установленную тайм-аутом набора, и довольно чистая.

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

Можно также использовать его для функций стиля CPS...

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

Идею можно также расширить, чтобы проверить более подробную информацию - например, аргументы и this - путем размещения обертки arround done. Например, благодаря этому ответу я могу сделать...

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

Просто придерживайтесь:

this.timeout(<time ms>);

в верхней части вашего оператора:

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

Ответ 5

Поздно к вечеринке здесь, но я столкнулся именно с этой проблемой и придумал другое решение. Брет признал, что ответ хороший, но я обнаружил, что он повредил при запуске моего полного набора тестов для мокко, выбросив ошибку done() called multiple times, которую я в конечном итоге отказался от попыток устранения неполадок. Ответ Meryl поставил меня на путь к собственному решению, которое также использует 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 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

Лучшее решение вместо 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
})

Как вы можете видеть, ключ заключается в том, чтобы свернуть решение с помощью: (... args) = > resolve (mySpy (... args)).

Таким образом, PROMIS новый Promise(). then() разрешен ТОЛЬКО после того, как будет вызван обратный вызов.

Но после вызова callback вы уже можете проверить, что вы ожидали от него.

Преимущества:

  • Нам не нужно угадывать тайм-аут, чтобы ждать, пока не будет запущено событие (в случае многих описаний() и его()), не зависящих от производительности компьютера.
  • и тесты будут быстрее проходить

Ответ 7

Я делаю это, оборачивая событие в Обещание:

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