英文原文:http://emberjs.com/guides/testing/testing-components/
单元测试方案和计算属性与之前单元测试基础中说明的相同,因为Ember.Component集成自Ember.Object。
设置
在测试组件之前,需要确定测试应用的div已经加到测试的html文件中:
<!-- as of time writing, ID attribute needs to be named exactly ember-testing --><div id="ember-testing"></div>
此外还需要通知Ember使用这个元素来重新渲染应用。
App.rootElement = '#ember-testing'
组件可以使用moduleForComponent助手来进行测试。下面是一个简单的Ember控件:
App.PrettyColorComponent = Ember.Component.extend({classNames: ['pretty-color'],attributeBindings: ['style'],style: function() {return 'color: ' + this.get('name') + ';';}.property('name')});
其对应的Handlebars模板:
Pretty Color: {{name}}
对这个控件进行单元测试可以使用moduleForComponent助手。助手将通过名称来查找这个组件(pretty-color)和其模板(如果存在)。
moduleForComponent('pretty-color');
现在每个测试有了一个subject()函数,该函数是组件工厂的create方法的一个别名。
下面是测试改变组件的颜色后,看看HTML是否重新渲染了。
test('changing colors', function(){// this.subject() is available because we used moduleForComponentvar component = this.subject();// we wrap this with Ember.run because it is an async functionEmber.run(function(){component.set('name','red');});// first call to $() renders the component.equal(this.$().attr('style'), 'color: red;');// another async function, so we need to wrap it with Ember.runEmber.run(function(){component.set('name', 'green');});equal(this.$().attr('style'), 'color: green;');});
另外一个可能的测试是检查组件的模板是否被正确地渲染了。
test('template is rendered with the color name', function(){// this.subject() is available because we used moduleForComponentvar component = this.subject();// first call to $() renders the component.equal($.trim(this.$().text()), 'Pretty Color:');// we wrap this with Ember.run because it is an async functionEmber.run(function(){component.set('name', 'green');});equal($.trim(this.$().text()), 'Pretty Color: green');});
在线示例
与DOM中的组件交互
Ember组件可以方便的用来创建有力的、交互的、自包含的自定义HTML元素。正因为此,不仅测试组件自身的方法很重要,测试用户与组件的交互也一样的重要。
下面看一个非常简单的组件,该组件只是在被点击时设置自己的标题:
App.MyFooComponent = Em.Component.extend({title:'Hello World',actions:{updateTitle: function(){this.set('title', 'Hello Ember World');}}});
下面将使用集成测试助手来与渲染的组件进行交互。
moduleForComponent('my-foo', 'MyFooComponent');test('clicking link updates the title', function() {var component = this.subject();// append the component to the DOMthis.append();// assert default stateequal(find('h2').text(), 'Hello World');// perform click actionclick('button');andThen(function() { // wait for async helpers to completeequal(find('h2').text(), 'Hello Ember World');});});
在线示例
具有内置布局的组件
一些组件并不适用一个分离的模板。模板可以通过布局属性嵌入在组件代码中。例如:
App.MyFooComponent = Ember.Component.extend({// layout supercedes template when renderedlayout: Ember.Handlebars.compile("<h2>I'm a little {{noun}}</h2><br/>" +"<button {{action 'clickFoo'}}>Click Me</button>"),noun: 'teapot',actions:{changeName: function(){this.set('noun', 'embereño');}}});
在本例中,也将对于组件的交互进行测试。
moduleForComponent('my-foo', 'MyFooComponent');test('clicking link updates the title', function() {var component = this.subject();// append the component to the DOMthis.append();// assert default stateequal(find('h2').text(), "I'm a little teapot");// perform click actionclick('button');andThen(function() { // wait for async helpers to completeequal(find('h2').text(), "I'm a little embereño");});});
在线示例
程序化组件交互
另外一种测试组件的方法是通过直接调用组件函数来取代与DOM进行交互。这里采用与之前相同的例子作为测试的对象,不同的是通过编码直接进行测试:
moduleForComponent('my-foo', 'MyFooComponent');test('clicking link updates the title', function() {var component = this.subject();// append the component to the DOM, returns DOM instancevar $component = this.append();// assert default stateequal($component.find('h2').text(), "I'm a little teapot");// send action programmaticallyEm.run(function(){component.send('changeName');});equal($component.find('h2').text(), "I'm a little embereño");});
在线示例
组件中sendAction验证
组件经常使用sendAction,这是与Ember应用交互的一种方式。下面是一个简单的组件,当一个按钮被点击时,发送internalAction操作:
App.MyFooComponent = Ember.Component.extend({layout:Ember.Handlebars.compile("<button {{action 'doSomething'}}></button>"),actions:{doSomething: function(){this.sendAction('internalAction');}}});
在测试中,将创建一个僵尸对象,用来接收组件发送的操作。
moduleForComponent('my-foo', 'MyFooComponent');test('trigger external action when button is clicked', function() {// tell our test to expect 1 assertionexpect(1);// component instancevar component = this.subject();// component dom instancevar $component = this.append();var targetObject = {externalAction: function(){// we have the assertion here which will be// called when the action is triggeredok(true, 'external Action was called!');}};// setup a fake external action to be called when// button is clickedcomponent.set('internalAction', 'externalAction');// set the targetObject to our dummy object (this// is where sendAction will send it's action to)component.set('targetObject', targetObject);// click the buttonclick('button');});
在线示例
使用其他组件的组件
有时候将组件拆分为父子组件更容易维护,下面是一个简单的例子:
App.MyAlbumComponent = Ember.Component.extend({tagName: 'section',layout: Ember.Handlebars.compile("<section>" +" <h3>{{title}}</h3>" +" {{yield}}" +"</section>"),titleBinding: ['title']});App.MyKittenComponent = Ember.Component.extend({tagName: 'img',attributeBindings: ['width', 'height', 'src'],src: function() {return 'http://placekitten.com/' + this.get('width') + '/' + this.get('height');}.property('width', 'height')});
使用这个组件可能如下面代码所示:
{{#my-album title="Cats"}}{{my-kitten width="200" height="300"}}{{my-kitten width="100" height="100"}}{{my-kitten width="50" height="50"}}{{/my-album}}
通过needs回调来测试这些包含子组件的组件非常容易。
moduleForComponent('my-album', 'MyAlbumComponent', {needs: ['component:my-kitten']});test('renders kittens', function() {expect(2);// component instancevar component = this.subject({template: Ember.Handlebars.compile('{{#my-album title="Cats"}}' +' {{my-kitten width="200" height="300"}}' +' {{my-kitten width="100" height="100"}}' +' {{my-kitten width="50" height="50"}}' +'{{/my-album}}')});// append component to the domvar $component = this.append();// perform assertionsequal($component.find('h3:contains("Cats")').length, 1);equal($component.find('img').length, 3);});
