2013-02-15 54 views
14

我們正在學習Ember.js。我們做所有的TDD開發,並希望Ember.js也不例外。我們有構建Backbone.js應用程序測試驅動的經驗,所以我們熟悉使用Jasmine或Mocha/Chai測試前端代碼。如何在ember.js中單元測試視圖?

當了解如何測試視圖時,當視圖的模板使用#linkTo語句時,我們遇到了問題。不幸的是,我們無法找到好的測試例子和實踐。這個要點是我們的追求,以獲得答案如何體面地單元測試燼應用程序。

在看着test for linkTo in Ember.js source code時,我們注意到它包含一個完整的支持#linkTo的支持應用程序的應用程序。這是否意味着我們不能在測試模板時殘留這種行爲?

如何使用模板渲染爲燼視圖創建測試?

這裏是a gist與我們的測試和一個模板,將使測試通過,並使其失敗的模板。

view_spec.js.coffee

# This test is made with Mocha/Chai, 
# With the chai-jquery and chai-changes extensions 

describe 'TodoItemsView', -> 

    beforeEach -> 
    testSerializer = DS.JSONSerializer.create 
     primaryKey: -> 'id' 

    TestAdapter = DS.Adapter.extend 
     serializer: testSerializer 
    TestStore = DS.Store.extend 
     revision: 11 
     adapter: TestAdapter.create() 

    TodoItem = DS.Model.extend 
     title: DS.attr('string') 

    store = TestStore.create() 
    @todoItem = store.createRecord TodoItem 
     title: 'Do something' 

    @controller = Em.ArrayController.create 
     content: [] 

    @view = Em.View.create 
     templateName: 'working_template' 
     controller: @controller 

    @controller.pushObject @todoItem 

    afterEach -> 
    @view.destroy() 
    @controller.destroy() 
    @todoItem.destroy() 

    describe 'amount of todos', -> 

    beforeEach -> 
     # $('#konacha') is a div that gets cleaned between each test 
     Em.run => @view.appendTo '#konacha' 

    it 'is shown', -> 
     $('#konacha .todos-count').should.have.text '1 things to do' 

    it 'is livebound', -> 
     expect(=> $('#konacha .todos-count').text()).to.change.from('1 things to do').to('2 things to do').when => 
     Em.run => 
      extraTodoItem = store.createRecord TodoItem, 
      title: 'Moar todo' 
      @controller.pushObject extraTodoItem 

broken_template.handlebars

<div class="todos-count"><span class="todos">{{length}}</span> things to do</div> 

{{#linkTo "index"}}Home{{/linkTo}} 

working_template.handlebars

<div class="todos-count"><span class="todos">{{length}}</span> things to do</div> 
+1

基本上你會遇到麻煩實例化孤立的東西。 Ember希望你的整個應用程序能夠正常運行這不是一個答案,但總的來說,我更喜歡集成測試(請參閱[本視頻](http://www.youtube.com/watch?v=heK78M6Ql9Q))單元測試。我已經看到人們試圖孤立地測試視圖,即使它起作用,看起來也不是一個好策略。請參閱http://www.slideshare.net/jo_liss/testing-ember-apps/27瞭解其背後的一些基本原理。 – 2013-02-16 23:20:05

回答

9

我們的解決方案是基本上加載整個應用程序,但儘可能隔離我們的測試對象。例如,

describe('FooView', function() { 
    beforeEach(function() { 
    this.foo = Ember.Object.create(); 
    this.subject = App.FooView.create({ foo: this.foo }); 
    this.subject.append(); 
    }); 

    afterEach(function() { 
    this.subject && this.subject.remove(); 
    }); 

    it("renders the foo's favoriteFood", function() { 
    this.foo.set('favoriteFood', 'ramen'); 
    Em.run.sync(); 
    expect(this.subject.$().text()).toMatch(/ramen/); 
    }); 
}); 

也就是說,路由器和其它全局可用,所以它不是完全隔離,但我們可以在雙打輕鬆地發送的東西下測試更接近目標。

如果你真的想路由器隔離的linkTo幫助查找它作爲controller.router,所以你可以做你可以處理這個

this.router = { 
    generate: jasmine.createSpy(...) 
}; 

this.subject = App.FooView.create({ 
    controller: { router: this.router }, 
    foo: this.foo 
}); 
1

一種方法是創建用於linkTo助手,然後存根在之前的塊中使用它。這將繞過真正linkTo的所有額外要求(例如路由),並讓您專注於視圖的內容。以下是我如何做的:

// Test helpers 
TEST.stubLinkToHelper = function() { 
    if (!TEST.originalLinkToHelper) { 
     TEST.originalLinkToHelper = Ember.Handlebars.helpers['link-to']; 
    } 
    Ember.Handlebars.helpers['link-to'] = function(route) { 
     var options = [].slice.call(arguments, -1)[0]; 
     return Ember.Handlebars.helpers.view.call(this, Em.View.extend({ 
      tagName: 'a', 
      attributeBindings: ['href'], 
      href: route 
     }), options); 
    }; 
}; 

TEST.restoreLinkToHelper = function() { 
    Ember.Handlebars.helpers['link-to'] = TEST.originalLinkToHelper; 
    TEST.originalLinkToHelper = null; 
}; 

// Foo test 
describe('FooView', function() { 
    before(function() { 
     TEST.stubLinkToHelper(); 
    }); 

    after(function() { 
     TEST.restoreLinkToHelper(); 
    }); 

    it('renders the favoriteFood', function() { 
     var view = App.FooView.create({ 
      context: { 
       foo: { 
        favoriteFood: 'ramen' 
       } 
      } 
     }); 

     Em.run(function() { 
      view.createElement(); 
     }); 

     expect(view.$().text()).to.contain('ramen'); 
    }); 
});