2013-05-30 63 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'來自己設置超時。摩卡有超時設施。 'assert(true)'是100%無用的。摩卡甚至不知道它發生了。如果你想讓測試失敗,你可以拋出任何你想要的舊異常。不需要使用'assert(false)'。但在這種情況下,奧利福特已經指出了一種更好的方式:'完成(新錯誤(...))'。 – Louis

+0

@路易斯看起來更好嗎? –

+0

是的,雖然現在我認爲這個問題很糟糕。我之前並沒有仔細研究它,但正如對問題的評論所表明的,OP所展示的測試應該超時而不是「崩潰」。這裏的答案在功能上與OP所做的不同。你有'this.timeout(1000)',但摩卡的默認超時時間爲2000,所以我們永遠不會沒有超時運行。所有關於這個問題的答案要麼在做OP已經做的事情,要麼包括不必要的扭曲,以試圖解決一個不存在的問題。 – Louis

12

編輯9月30日:

我看到我的回答被接受爲正確的答案,但佈雷特Copeland的技術(見下面的回答)僅僅是更好,因爲它是在測試是成功的更快,這會是這樣大多數情況下,您會將測試作爲測試套件的一部分進行測試


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

這裏我們檢查的不僅是事件觸發,而且事件在超時期間只觸發一次。

Sinon還支持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'); 
    }); 

否則,佈雷特Copeland的技術總是快於「成功」的情況下(有希望常見的情況),因爲如果事件被觸發,它能夠立即呼叫done

+0

我只會注意到,這樣做的缺點是測試總是需要一秒鐘(或任何超時),即使事件僅在3毫秒內觸發。這可能是一個很大的缺點,如果你正在運行很多這些樣式測試,因爲它可能導致它們運行速度降低數百倍。您可以使用較短的超時時間,但根據事件的性質,您可能會增加「錯誤否定」的風險。所以這確實是一個個案的決定。 –

+0

佈雷特,好點。我其實並沒有意識到這種差異!那麼你的解決方案顯然要優越。我主要發佈這個來展示Sinon,因爲這在測試回調方面用了很多。在這種情況下,它可能不太合適。此外,測試事件不會觸發不止一次,如果多次調用「完成」,Mocha會自動覆蓋測試失敗。 –

+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

遲到了這裏,但我確切地面對這個問題,並與其他解決方案上來。佈雷特接受的答案是一個很好的答案,但我發現在運行我的全套摩卡測試套件時發生了嚴重破壞,拋出了錯誤done() called multiple times,我最終放棄嘗試排除故障。梅麗爾的回答讓我走上了我自己的解決方案,它也使用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(); 
    }) 
}) 
+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 - 通過把一個包裝以防萬一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'的重點。如果你在第一個'process.nextTick'之前移動'testEmitter.on',那麼通過直接調用'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 
}) 

正如你所看到的,關鍵是用解析包裝回調:(... args)=>解析(mySpy(... args))

因此,PROMIS 新無極()。然後,()只有之後調用回調解決

但是一旦調用回調函數,您就可以測試,您對他的期望是什麼。

優點

  • 我們不需要去猜測超時​​等待,直到事件被觸發(在許多介紹情況()和()),不依賴於計算機的性能比較
  • 並且測試將更快通過
+0

在某些情況下,是的,承諾是要走的路,但在這裏使用承諾沒有任何好處。這樣做只是增加了不必要的解決方案。 – Louis

相關問題