home
  • Blog
1.10
  • Getting Started
  • Getting Ember
  • Concepts
  • The Object Model
  • Application
  • Templates
  • Routing
  • Components
  • Controllers
  • Models
  • Views
  • Enumerables
  • Testing
    • Introduction
    • Integration Tests
    • Test Helpers
    • Testing User Interaction
    • Unit Testing Basics
    • Unit Test Helpers
    • Testing Components
    • Testing Controllers
    • Testing Routes
    • Testing Models
    • Automating Tests with Runners
  • Configuring Ember.js
  • Cookbook
  • Understanding Ember.js
  • Contributing to Ember.js
Old Guides - You are viewing the guides for Ember v1.10.0.
Go to v5.0.0

Testing Components

Edit pencil

Unit testing methods and computed properties follows previous patterns shown in Unit Testing Basics because Ember.Component extends Ember.Object.

Setup

Before testing components, be sure to add the testing application div to your testing html file:

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

You'll also need to tell Ember to use this element for rendering the application:

App.rootElement = '#ember-testing'

You can test components using the moduleForComponent helper. Testing this simple Ember component:

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

... with an accompanying Handlebars template:

Pretty Color: {{name}}

Unit testing this component can be done using the moduleForComponent helper. This helper will find the component by name (pretty-color) and its template (if available).

moduleForComponent('pretty-color');

Now each test following the moduleForComponent call has a subject() function, which aliases the create method on the component factory.

We can test to make sure that changing the component's color property updates the rendered HTML:

test('changing colors', function() {

  // this.subject() is available because we used moduleForComponent
  var component = this.subject();

  // we wrap this with Ember.run because it is an async function
  Ember.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.run
  Ember.run(function() {
    component.set('name', 'green');
  });

  equal(this.$().attr('style'), 'color: green;');
});

We might also test this component to ensure the template is being rendered properly.

test('template is rendered with the color name', function() {

  // this.subject() is available because we used moduleForComponent
  var 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 function
  Ember.run(function() {
    component.set('name', 'green');
  });

  equal($.trim(this.$().text()), 'Pretty Color: green');
});

Live Example

Unit Testing Components

Interacting with Components in the DOM

Ember Components are a great way to create powerful, interactive, self-contained custom HTML elements. Because of this, it is important to test the component's methods and the user's interaction with the component.

Let's look at a very simple component that simply sets its own title when clicked:

App.MyFooComponent = Em.Component.extend({
  title:'Hello World',

  actions: {
    updateTitle: function() {
      this.set('title', 'Hello Ember World');
    }
  }
});

We would use Integration Test Helpers to interact with the rendered component and test its behavior:

moduleForComponent('my-foo', 'MyFooComponent');

test('clicking link updates the title', function() {
  var component = this.subject();

  // append the component to the DOM
  this.append();

  // assert default state
  equal(find('h2').text(), 'Hello World');

  // perform click action
  click('button');

  andThen(function() { // wait for async helpers to complete
    equal(find('h2').text(), 'Hello Ember World');
  });
});

Live Example

Unit Testing Components

Components with embedded layout

Some components do not use a separate template. Instead, the template is embedded into the component via the layout property and inline HTML. For example:

App.MyFooComponent = Ember.Component.extend({

  // layout supercedes template when rendered
  layout: Ember.Handlebars.compile(
    "<h2>I'm a little {{noun}}</h2><br/>" +
    "<button {{action 'changeName'}}>Click Me</button>"
  ),

  noun: 'teapot',

  actions: {
    changeName: function() {
      this.set('noun', 'embereño');
    }
  }
});

In this example, we would still use Integration Test Helpers to test a user's DOM interaction.

moduleForComponent('my-foo', 'MyFooComponent');

test('clicking link updates the title', function() {
  var component = this.subject();

  // append the component to the DOM
  this.append();

  // assert default state
  equal(find('h2').text(), "I'm a little teapot");

  // perform click action
  click('button');

  andThen(function() { // wait for async helpers to complete
    equal(find('h2').text(), "I'm a little embereño");
  });
});

Live Example

Testing Components with Built-in Layout

Programmatically interacting with components

Another way we can test our components is to perform function calls directly on the component instead of through DOM interaction. Let's use the same component example from above, but perform the tests programatically:

moduleForComponent('my-foo', 'MyFooComponent');

test('sending changeName message updates the title', function() {
  var component = this.subject();

  // append the component to the DOM, returns DOM instance
  var $component = this.append();

  // assert default state
  equal($component.find('h2').text(), "I'm a little teapot");

  // send action programmatically
  Ember.run(function() {
    component.send('changeName');
  });

  equal($component.find('h2').text(), "I'm a little embereño");
});

Live Example

Programatically Testing Components

sendAction validation in components

Components often utilize sendAction, which is a way to interact with the Ember application. Here's a simple component that sends the action internalAction when a button is clicked:

App.MyFooComponent = Ember.Component.extend({
  layout:Ember.Handlebars.compile("<button {{action 'doSomething'}}></button>"),

  actions: {
    doSomething: function() {
      this.sendAction('internalAction');
    }
  }
});

In our test, we will create a test double (dummy object) that receives the action being sent by the component.

moduleForComponent('my-foo', 'MyFooComponent');

test('trigger external action when button is clicked', function() {
  // tell our test to expect 1 assertion
  expect(1);

  // component instance
  var component = this.subject();

  // component dom instance
  var $component = this.append();

  var targetObject = {
    externalAction: function() {
      // we have the assertion here which will be
      // called when the action is triggered
      ok(true, 'external Action was called!');
    }
  };

  // setup a fake external action to be called when
  // button is clicked
  component.set('internalAction', 'externalAction');

  // set the targetObject to our dummy object (this
  // is where sendAction will send its action to)
  component.set('targetObject', targetObject);

  // click the button
  click('button');
});

Live Example

sendAction Validation in Components

Components Using Other Components

Sometimes components are easier to maintain if they're broken up into parent and child components. Here is a simple example:

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

Usage of this component might look something like this:

{{#my-album title="Cats"}}
  {{my-kitten width="200" height="300"}}
  {{my-kitten width="100" height="100"}}
  {{my-kitten width="50" height="50"}}
{{/my-album}}

Using the needs callback greatly simplifies testing components with a parent-child relationship.

moduleForComponent('my-album', 'MyAlbumComponent', {
  needs: ['component:my-kitten']
});

test('renders kittens', function() {
  expect(2);

  // component instance
  var component = this.subject({
    title: 'Cats',
    template: Ember.Handlebars.compile(
      '{{my-kitten width="200" height="300"}}' +
      '{{my-kitten width="100" height="100"}}' +
      '{{my-kitten width="50" height="50"}}'
    )
  });

  // append component to the dom
  var $component = this.append();

  // perform assertions
  equal($component.find('h3:contains("Cats")').length, 1);
  equal($component.find('img').length, 3);
});

Live Example

Components with Embedded Components

left arrow
Unit Test Helpers
Testing Controllers
right arrow
On this page

  • Setup
  • Live Example
  • Interacting with Components in the DOM
  • Live Example
  • Components with embedded layout
  • Live Example
  • Programmatically interacting with components
  • Live Example
  • sendAction validation in components
  • Live Example
  • Components Using Other Components
  • Live Example
Team Sponsors Security Legal Branding Community Guidelines
Twitter GitHub Discord Mastodon

If you want help you can contact us by email, open an issue, or get realtime help by joining the Ember Discord.

© Copyright 2023 - Tilde Inc.
Ember.js is free, open source and always will be.


Ember is generously supported by
blue