英文原文:http://emberjs.com/guides/testing/testing-components/

单元测试方案和计算属性与之前单元测试基础中说明的相同,因为Ember.Component集成自Ember.Object

设置

在测试组件之前,需要确定测试应用的div已经加到测试的html文件中:

  1. <!-- as of time writing, ID attribute needs to be named exactly ember-testing -->
  2. <div id="ember-testing"></div>

此外还需要通知Ember使用这个元素来重新渲染应用。

  1. App.rootElement = '#ember-testing'

组件可以使用moduleForComponent助手来进行测试。下面是一个简单的Ember控件:

  1. App.PrettyColorComponent = Ember.Component.extend({
  2. classNames: ['pretty-color'],
  3. attributeBindings: ['style'],
  4. style: function() {
  5. return 'color: ' + this.get('name') + ';';
  6. }.property('name')
  7. });

其对应的Handlebars模板:

  1. Pretty Color: {{name}}

对这个控件进行单元测试可以使用moduleForComponent助手。助手将通过名称来查找这个组件(pretty-color)和其模板(如果存在)。

  1. moduleForComponent('pretty-color');

现在每个测试有了一个subject()函数,该函数是组件工厂的create方法的一个别名。

下面是测试改变组件的颜色后,看看HTML是否重新渲染了。

  1. test('changing colors', function(){
  2. // this.subject() is available because we used moduleForComponent
  3. var component = this.subject();
  4. // we wrap this with Ember.run because it is an async function
  5. Ember.run(function(){
  6. component.set('name','red');
  7. });
  8. // first call to $() renders the component.
  9. equal(this.$().attr('style'), 'color: red;');
  10. // another async function, so we need to wrap it with Ember.run
  11. Ember.run(function(){
  12. component.set('name', 'green');
  13. });
  14. equal(this.$().attr('style'), 'color: green;');
  15. });

另外一个可能的测试是检查组件的模板是否被正确地渲染了。

  1. test('template is rendered with the color name', function(){
  2. // this.subject() is available because we used moduleForComponent
  3. var component = this.subject();
  4. // first call to $() renders the component.
  5. equal($.trim(this.$().text()), 'Pretty Color:');
  6. // we wrap this with Ember.run because it is an async function
  7. Ember.run(function(){
  8. component.set('name', 'green');
  9. });
  10. equal($.trim(this.$().text()), 'Pretty Color: green');
  11. });

在线示例

组件单元测试

与DOM中的组件交互

Ember组件可以方便的用来创建有力的、交互的、自包含的自定义HTML元素。正因为此,不仅测试组件自身的方法很重要,测试用户与组件的交互也一样的重要。

下面看一个非常简单的组件,该组件只是在被点击时设置自己的标题:

  1. App.MyFooComponent = Em.Component.extend({
  2. title:'Hello World',
  3. actions:{
  4. updateTitle: function(){
  5. this.set('title', 'Hello Ember World');
  6. }
  7. }
  8. });

下面将使用集成测试助手来与渲染的组件进行交互。

  1. moduleForComponent('my-foo', 'MyFooComponent');
  2. test('clicking link updates the title', function() {
  3. var component = this.subject();
  4. // append the component to the DOM
  5. this.append();
  6. // assert default state
  7. equal(find('h2').text(), 'Hello World');
  8. // perform click action
  9. click('button');
  10. andThen(function() { // wait for async helpers to complete
  11. equal(find('h2').text(), 'Hello Ember World');
  12. });
  13. });

在线示例

组件单元测试

具有内置布局的组件

一些组件并不适用一个分离的模板。模板可以通过布局属性嵌入在组件代码中。例如:

  1. App.MyFooComponent = Ember.Component.extend({
  2. // layout supercedes template when rendered
  3. layout: Ember.Handlebars.compile(
  4. "<h2>I'm a little {{noun}}</h2><br/>" +
  5. "<button {{action 'clickFoo'}}>Click Me</button>"
  6. ),
  7. noun: 'teapot',
  8. actions:{
  9. changeName: function(){
  10. this.set('noun', 'embereño');
  11. }
  12. }
  13. });

在本例中,也将对于组件的交互进行测试。

  1. moduleForComponent('my-foo', 'MyFooComponent');
  2. test('clicking link updates the title', function() {
  3. var component = this.subject();
  4. // append the component to the DOM
  5. this.append();
  6. // assert default state
  7. equal(find('h2').text(), "I'm a little teapot");
  8. // perform click action
  9. click('button');
  10. andThen(function() { // wait for async helpers to complete
  11. equal(find('h2').text(), "I'm a little embereño");
  12. });
  13. });

在线示例

测试带内置布局的组件

程序化组件交互

另外一种测试组件的方法是通过直接调用组件函数来取代与DOM进行交互。这里采用与之前相同的例子作为测试的对象,不同的是通过编码直接进行测试:

  1. moduleForComponent('my-foo', 'MyFooComponent');
  2. test('clicking link updates the title', function() {
  3. var component = this.subject();
  4. // append the component to the DOM, returns DOM instance
  5. var $component = this.append();
  6. // assert default state
  7. equal($component.find('h2').text(), "I'm a little teapot");
  8. // send action programmatically
  9. Em.run(function(){
  10. component.send('changeName');
  11. });
  12. equal($component.find('h2').text(), "I'm a little embereño");
  13. });

在线示例

程序化组件测试

组件中sendAction验证

组件经常使用sendAction,这是与Ember应用交互的一种方式。下面是一个简单的组件,当一个按钮被点击时,发送internalAction操作:

  1. App.MyFooComponent = Ember.Component.extend({
  2. layout:Ember.Handlebars.compile("<button {{action 'doSomething'}}></button>"),
  3. actions:{
  4. doSomething: function(){
  5. this.sendAction('internalAction');
  6. }
  7. }
  8. });

在测试中,将创建一个僵尸对象,用来接收组件发送的操作。

  1. moduleForComponent('my-foo', 'MyFooComponent');
  2. test('trigger external action when button is clicked', function() {
  3. // tell our test to expect 1 assertion
  4. expect(1);
  5. // component instance
  6. var component = this.subject();
  7. // component dom instance
  8. var $component = this.append();
  9. var targetObject = {
  10. externalAction: function(){
  11. // we have the assertion here which will be
  12. // called when the action is triggered
  13. ok(true, 'external Action was called!');
  14. }
  15. };
  16. // setup a fake external action to be called when
  17. // button is clicked
  18. component.set('internalAction', 'externalAction');
  19. // set the targetObject to our dummy object (this
  20. // is where sendAction will send it's action to)
  21. component.set('targetObject', targetObject);
  22. // click the button
  23. click('button');
  24. });

在线示例

组件中sendAction验证

使用其他组件的组件

有时候将组件拆分为父子组件更容易维护,下面是一个简单的例子:

  1. App.MyAlbumComponent = Ember.Component.extend({
  2. tagName: 'section',
  3. layout: Ember.Handlebars.compile(
  4. "<section>" +
  5. " <h3>{{title}}</h3>" +
  6. " {{yield}}" +
  7. "</section>"
  8. ),
  9. titleBinding: ['title']
  10. });
  11. App.MyKittenComponent = Ember.Component.extend({
  12. tagName: 'img',
  13. attributeBindings: ['width', 'height', 'src'],
  14. src: function() {
  15. return 'http://placekitten.com/' + this.get('width') + '/' + this.get('height');
  16. }.property('width', 'height')
  17. });

使用这个组件可能如下面代码所示:

  1. {{#my-album title="Cats"}}
  2. {{my-kitten width="200" height="300"}}
  3. {{my-kitten width="100" height="100"}}
  4. {{my-kitten width="50" height="50"}}
  5. {{/my-album}}

通过needs回调来测试这些包含子组件的组件非常容易。

  1. moduleForComponent('my-album', 'MyAlbumComponent', {
  2. needs: ['component:my-kitten']
  3. });
  4. test('renders kittens', function() {
  5. expect(2);
  6. // component instance
  7. var component = this.subject({
  8. template: Ember.Handlebars.compile(
  9. '{{#my-album title="Cats"}}' +
  10. ' {{my-kitten width="200" height="300"}}' +
  11. ' {{my-kitten width="100" height="100"}}' +
  12. ' {{my-kitten width="50" height="50"}}' +
  13. '{{/my-album}}'
  14. )
  15. });
  16. // append component to the dom
  17. var $component = this.append();
  18. // perform assertions
  19. equal($component.find('h3:contains("Cats")').length, 1);
  20. equal($component.find('img').length, 3);
  21. });

在线示例

带嵌套组件的组件