簡體   English   中英

對 Nodejs 中發出的事件進行單元測試的最佳方法是什么?

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

我正在編寫一堆 mocha 測試,我想測試發出的特定事件。 目前,我正在這樣做:

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

但是,如果事件從不發出,它會使測試套件崩潰,而不是使該測試失敗。

測試這個的最好方法是什么?

如果您可以保證事件應在一定時間內觸發,則只需設置超時。

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

如果您無法保證事件何時觸發,那么它可能不適合進行單元測試。

編輯9月30日:

我認為我的答案被認為是正確的答案,但Bret Copeland的技術(見下面的答案)更好,因為它在測試成功時更快,大多數時候你作為測試套件的一部分運行測試就是這種情況。


布雷特科普蘭的技術是正確的。 您也可以采用不同的方式:

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

在這里,我們檢查不僅事件被觸發,而且如果事件在超時期間僅觸發了一次。

興農還支持calledWithcalledOn ,檢查使用了什么樣的參數和功能方面。

請注意,如果您希望事件與觸發事件的操作同步觸發(中間沒有異步調用),則可以使用零超時。 只有在執行異步調用之間需要很長時間才能完成時,才需要超時1000 ms。 很可能不是這樣的。

實際上,當事件保證與導致它的操作同步觸發時,您可以將代碼簡化為

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

否則,Bret Copeland的技術在“成功”案例中總是更快(希望是常見的情況),因為如果事件被觸發,它能夠立即調用done

此方法確保等待的最短時間,但是套件超時設置的最大機會非常干凈。

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

});

堅持:

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

在這里晚會,但我正面臨這個問題,並提出了另一種解決方案。 Bret接受的答案很好,但我發現它在運行我的完整mocha測試套件時會造成嚴重破壞,拋出錯誤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'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();
    })
})

更好的解決方案而不是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
})

正如您所看到的,關鍵是使用resolve包裝calback: (... args)=> resolve(mySpy(... args))

因此,PROMIS new Promise()。then() 在被稱為回調之后被解析。

但是一旦調用了回調,你就可以測試你對他的期望了。

優點

  • 我們不需要猜測超時等待事件被觸發(如果有很多describe()和它的()),不依賴於計算機的性能
  • 並且測試將更快通過

我通過將事件包裝在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;
    }
}

我建議使用once()來獲得更簡單的解決方案,尤其是如果您喜歡 async/await 風格:

const once = require('events').once
// OR import { once } from 'events'

it('should emit an some_event', async function() {
    this.timeout(1000); //timeout with an error if await waits more than 1 sec

    p = once(myObj, 'some_event')
    // execute some code which should trigger 'some_event' on myObj
    await p
});

如果您需要檢查值:

[obj] = await p
assert.equal(obj.a, 'a')

最后,如果您使用的是打字稿,以下幫助程序可能會很方便:

// Wait for event and return first data item
async function onceTyped<T>(event: string): Promise<T> {
    return <T>(await once(myObj, event))[0]
}

像這樣使用:

 const p = onceTyped<SomeEvent>(myObj, 'some_event')
 // execute some code which should trigger 'some_event' on myObj
 const someEvent = await p // someEvent has type SomeEvent
 assert.equal(someEvent.a, 'a')

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM