2011-06-24 78 views
20

我在嘗試測試視圖是否正確綁定事件時遇到了一些有趣的磨難。在主幹中,我們通常使用以下幾行代碼綁定到初始化方法中的事件:something.bind("change", this.render);。在我的測試中,我想確保這個綁定已經建立,所以我做了以下操作:用jasmine測試backbone.js應用程序 - 如何在視圖上測試模型綁定?

this.myView = new MyView(); 
spyOn(this.myView, "render");; 
this.legendView.groupData.trigger("change"); 
expect(this.legendView.render).toHaveBeenCalled(); 

但是,那不行。因爲綁定發生在MyView的初始化函數中,所以事件get在當時綁定到myView的渲染函數。所以,當你添加你的間諜時,它將包裝渲染函數,並將其設置回myView.render。但由第一個綁定產生的封閉仍然存在,我們完全茫然。那麼我們能做些什麼呢?我所做的,是將我的bind調用的一個單獨的功能,是這樣的:

myView = Backbone.View.extend({ 
initialize: function(){ 
    _.bindAll(this, "render"); 
    this.initialize_model_bindings(); 
}, 
initialize_model_bindings: function(){ 
    something.bind("change", this.render); 
}, 
render: function(){ //... } 
}); 

和我的測試則是這樣的:

this.myView = new MyView(); 
spyOn(this.myView, "render"); 
this.myView.initialize_model_bindings(); 
this.legendView.groupData.trigger("change"); 
expect(this.legendView.render).toHaveBeenCalled(); 

這工作,但我正在尋找一個更好的解決方案。謝謝

+0

您是否介意解釋這一點?「但是由第一個綁定創建的閉包仍然存在,我們完全茫然了」?我有同樣的問題,並試圖瞭解發生了什麼。謝謝。 –

+1

當您調用'new MyView()'時,initialize方法被調用,並且此時對象中存在的render函數綁定到change事件。從那時起,不可能訪問綁定到change事件的render函數 - 它被鎖定在閉包中。 – idbentley

+0

感謝您的澄清。 –

回答

5

而不是窺探回調,你可能會嘗試窺探something.bind。然後測試該綁定被稱爲w /適當的參數。到目前爲止,這對我很有用。我使用的是sinon.js而不是茉莉花的內置間諜。 sinon.js使得在一堆相同的方法調用中測試傳遞給方法調用的參數(例如,在視圖init中綁定的一堆調用)更容易。所以我沒有單獨測試這個想法,只是相信它應該是可能的。

spyOn(this.legendView.groupData, 'bind'); 
this.myView = new MyView(); 
expect(this.legendView.groupData.mostRecentCall.args).toEqual('change', this.myView.render); // example!! only works if testing a single call to bind or the last call in a series (ie mostRecentCall) 

和W/sinon.js

sinon.spy(this.legendView.groupData, 'bind'); 
this.myView = new MyView(); 
expect(this.legendView.groupData.bind.calledWith('change', this.myView.render); // works w/ any number of calls to bind 
3

我的間諜在我的渲染功能調用的函數解決了這個問題。因此,在你的例子:

myView = Backbone.View.extend({ 
    initialize: function(){ 
     _.bindAll(this, "render"); 
     something.bind("change", this.render); 
    }, 
    someOtherFunction: function(){}, //this function only called from render 
    render: function(){ this.someOtherFunction(); /* rest of render function */ } 
}); 

測試看起來像:

this.myView = new MyView(); 
spyOn(this.myView, "someOtherFunction"); 
this.myView.something.trigger("change"); 
expect(this.myView.someOtherFunction).toHaveBeenCalled(); 

然後我寫了一個單獨的測試無論什麼someOtherFunction做。

+0

這似乎不是一個可怕的解決方案,但我發現我的渲染功能變化相當頻繁。我覺得如果我這樣做,我會更新我的規格。 – idbentley

2

你應該考慮看Sinon.js。你可以存根/模擬render()調用,甚至不必擔心'someOtherFunction()'。

1

這可能是過於緊密耦合與骨幹內部,但你可以手動檢查回調鏈:

expect(this.legendView.groupData._callbacks['change']).toContain(this.myView.render) 
12

我已成功地實現這一目標使用原型修補。在創建視圖的實例之前,構造函數的原型是spyOn

spyOn(MyView.prototype, 'changeSelected'); 
var view = new MyView(); 
view.selectSomething(); 
expect(view.changeSelected).toHaveBeenCalled(); 
+0

由於某種原因,這隻對我使用'sinon.spy(...)'。感謝這個提示! – fifigyuri

+0

如果sinon的api在去年發生了變化,我不會感到驚訝...... – mjtamlyn

0

我遇到了同樣的問題,從改變我的看法代碼:

this.model.on('change', this.render, this); 

到:

this.model.on('change', function() { 
    this.render(); 
}, this); 

我的茉莉花測試和預期一樣。

相關問題