2013-05-30 2 views
21

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

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

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

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

+1

Как код «сбой тестового набора»? Я ожидаю, что этот конкретный тест будет просто тайм-аут. Возможно, в других частях тестового кода есть проблемы. –

ответ

29

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

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

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

+0

Нет необходимости * использовать * setTimeout' для установки тайм-аута самостоятельно. У Mocha есть тайм-аут. 'assert (true)' на 100% бесполезен. Мокка не узнает, что это произошло. И если вы хотите сделать тест неудачным, вы можете просто выбросить любое старое исключение, которое вы хотите. Не нужно использовать 'assert (false)'. Но в этом случае Оли Форде уже указал еще лучший способ: «done (new Error (...))». – Louis

+0

@ Louis делает это лучше? –

+0

Да, хотя сейчас я думаю, что вопрос плохой. Я на самом деле не внимательно их рассматривал раньше, но, как указывает комментарий к вопросу, тест, который показал OP, должен был просто тайм-аут, а не «сбой». Ответ здесь не функционально отличается от того, что делает OP. У вас есть 'this.timeout (1000)', но Mocha имеет время ожидания по умолчанию 2000, поэтому мы никогда не работаем без тайм-аута. Все ответы по этому вопросу либо делают то, что делает OP, либо включают ненужные искажения, чтобы попытаться устранить несуществующую проблему. – Louis

12

Edit 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, если событие инициировано.

+0

Я просто хотел бы отметить, что недостатком такого способа является то, что тест всегда будет занимать одну секунду (или любой другой тайм-аут), даже если событие должно было срабатывать всего за 3 миллисекунды. Это может быть довольно большим недостатком, если вы используете множество этих тестов стиля, поскольку это может привести к тому, что они будут работать в сотни раз медленнее. Вы можете использовать более короткий тайм-аут, но вы увеличиваете риск «ложных негативов» в зависимости от характера событий. Так что это действительно решение в каждом конкретном случае. –

+0

Брет, хорошая точка. На самом деле я этого не осознал! Тогда ваше решение явно лучше. Я в основном разместил это, чтобы продемонстрировать Sinon, потому что это использовало много для тестирования обратных вызовов. В этом случае это может быть менее целесообразным. Кроме того, тестирование события, которое не срабатывает более одного раза, покрывается Mocha, автоматически не выполняющим проверку, если 'done' вызывается несколько раз. –

+0

Да, все, что вы опубликовали, действительно и ценится. Я просто хотел указать на эту разницу. –

3

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

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

Late к партии, но я столкнулся именно с этой проблемой и пришел к другому решению. Принимаемый ответ Брет - хороший, но я обнаружил, что он повредил при запуске моего полного набора тестов для мокко, выбросив ошибку done() called multiple times, и я в конечном итоге отказался от попытки устранения неполадок.Ответ Meryl поставил меня на пути к моему собственному решению, которое также использует sinon, но не требует использования тайм-аута. Путем простого обнуления метода emit() вы можете проверить, что он вызывается и проверяет его аргументы. Это предполагает, что ваш объект наследуется от класса EventEmitter от Node. Имя метода 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'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(); 
    }) 
}) 
+0

Это интересно, но я пытаюсь проверить события DOM. мысли о том, как это может работать? – ekkis

+1

Я не так разбираюсь в экзаменах на интерфейсных модулях, но думаю, что тот же метод будет работать. Это зависит от конкретного контекста ваших событий. Вы просто закроете тот метод, который эквивалентен 'emit'. Поэтому для jQuery я думаю, что вы бы сделали что-то вроде 'var $ el = $ (" # someElement ");' 'var eventStub = sinon.stub ($ el, 'trigger');' Это полностью спекулятивный и непроверенный, но, надеюсь, поможет вам в решении проблемы. – Ben

+0

спасибо за ответ. Я буду играть с этим. +1 – ekkis

4

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

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. Например, благодаря this answer я могу сделать ...

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

Да, это именно то, как это должно быть сделано. Нет смысла использовать 'setTimeout'. – Louis

+0

Я не вижу смысла делать 'process.nextTick'. Если вы переместите 'testEmitter.on' до первого' process.nextTick', все будет работать нормально, вызвав 'testEmitter.emit ('finish')' напрямую. – Louis

+0

@ Louis это просто для имитации реальной ситуации. Я тестирую объект, который выдает событие после выполнения задачи. Иногда задача синхронна, а иногда нет, но объект должен всегда действовать асинхронно, и это одна из характеристик, которую я тестирую. Если он возвращается синхронно, вышеуказанный тест завершится неудачей. Я мог бы также сделать это, подписавшись на событие после вызова объекта, но я сделал это таким образом, чтобы проверить, что, в общем, не имеет значения, является ли вызов до или после подписки. –

0

Лучше решение вместо sinon.timers является использование ES6 - Обещания:

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

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

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

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

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

  • нам не нужно угадать таймаут ждать, пока событие не срабатывает (в случае многих описывает() и его()), а не в зависимости от Perfomance компьютера
  • и тесты будут проходить быстрее
+0

В некоторых случаях да, обещания - это путь, но здесь нет никакой пользы от использования обещаний. Это просто добавляет ненужное оборудование в решение. – Louis

Смежные вопросы