Today we are going to look at testing our Ember application. Ember uses QUnit as its built in testing framework. You can choose a different javascript testing framework if you prefer, but we are going to stick with the defaults. For our purposes, we will first create a component then we will write some basic integration tests followed by a simple unit test.

Getting Started

We will start by creating an application.

$ ember new testable-app
$ cd testable-app

Once inside our new app's directory we will generate a component.

$ ember g component testable-widget

Now you can run your tests by typing ember test or you can run your test server by typing ember test -s and visiting http://localhost:7357. You will notice that on running your test, you will have several jshint tests that should all pass, and you will have one integration test. When we generated our component Ember also created for us a shell of an integration test. If we open up that test and take a look we'll be able to see what is going on. Open tests/integration/components/testable-widget-test.js

import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';

moduleForComponent('testable-widget', 'Integration | Component | testable widget', {
  integration: true
});

test('it renders passed values', function(assert) {
  // Set any properties with this.set('myProperty', 'value');
  // Handle any actions with this.on('myAction', function(val) { ... });

  this.render(hbs`{{testable-widget}}`);

  assert.equal(this.$().text().trim(), '');

  // Template block usage:
  this.render(hbs`
    {{#testable-widget}}
      template block text
    {{/testable-widget}}
  `);

  assert.equal(this.$().text().trim(), 'template block text');
});

Let's walk through this. This test section contains two assertions and for these two assertions we are rendering our component twice, once inline and once as a block. The first assertion assert.equal... is testing the inline render and the second is testing the block. We'll make our component render both inline and block, so we'll leave them both. The selector this.$() gives us access to the rendered component. I'm going to start by changing the tests so that both will fail.

this.render(hbs`{{testable-widget title="Widget Header" content="Some content"}}`);

assert.equal(this.$('h2').text().trim(), 'Widget Header');
assert.equal(this.$('.content-container').text().trim(), 'Some content');

// Template block usage:
this.render(hbs`
  {{#testable-widget title='Widget Block Title'}}
    The block text.
  {{/testable-widget}}
`);

assert.equal(this.$('h2').text().trim(), 'Widget Block Title');
assert.equal(this.$('.content-container').text().trim(), 'The block text.');

Now, if we run our tests, they will fail. Let's open our component template and make these tests pass. In app/templates/components/testable-widget.hbs:

<h2>{{title}}</h2>
<div class='content-container'>
  {{#if hasBlock}}
    {{yield}}
  {{else}}
    {{content}}
  {{/if}}
</div>

Here, we'll create a header with our passed in title. Then, we'll create a div with the content-container class. Finally, we'll add a handlebars if helper with the component's built in hasBlock property. If the component is rendered as a block we yield the content in the component block. If we have an inline component we will render the value that we passed in as with the content property.

Now, our tests will pass. Let's also add a test for default values if nothing is passed in. To do that we will construct a new failing test:

test('it renders default values if no values are passed in', function(assert) {

  this.render(hbs`{{testable-widget}}`);

  assert.equal(this.$('h2').text().trim(), 'Default Widget Header');
  assert.equal(this.$('.content-container').text().trim(), 'Default Content');
});

Now, let's update our component to pass the test.

In app/components/testable-widget.js:

import Ember from 'ember';

export default Ember.Component.extend({
  title: 'Default Widget Header',
  content: 'Default Content'
});

Now if we run our tests, they will pass. Let's add one more integration test before moving on to a unit test. We want to test an action that we are bubbling from our component to its parent. To do this we need to set up an action on the test parent to receive our action. this inside of an integration test for a component is the surrounding context, or rather the parent of the rendered component. So, if we want to test that a component sends its parent an action we just need to set up a receiving action on this in the test and we can confirm that we receive the params that we expect to receive. Let's make a contrived example where we send the component's parent its content as the parameter of an action.

test('when the send content button is clicked it triggers an action that sends the content to the parent', function(assert) {

  // We are setting a property on the parent
  this.set('outerContent', "This is some content.");

  // here we are setting up our action on the parent that will be called in our component
  this['actions']['receiveContent'] = (content) => {
    // here we are asserting that the content that we are passed from the action matches the content
    assert.deepEqual(content, this.get('outerContent'), 'The content value is property passed up to the parent');
  };

  // rendering our component
  this.render(hbs`{{testable-widget content=outerContent sendContent=(action 'receiveContent')}}`);

  // triggering the click action that will fire our action.
  this.$('#send-content-button').click();
});

To get this test to pass we first need to add a button with an action in our component's template.

In app/templates/components/testable-widget.hbs add the following:

<button id='send-content-button' {{action 'actionOnComponent'}}>Send Content</button>

Now, we can update our component's JavaScript to include the action:

import Ember from 'ember';

export default Ember.Component.extend({
  title: 'Default Widget Header',
  content: 'Default Content',
  actions: {
    actionOnComponent(){
      const content = this.get('content');
      this.get('sendContent')(content);
    }
  }
});

Now, all of our tests once again pass. Although Ember creates an integration test for us when we generate our component, we aren't limited to integration tests when testing components. We can also create a unit test. Let's say we want to add a computed property to our component that returns the first 40 characters of our content property. I believe we can test this more cleanly with a unit test. To use Ember's generators to create a unit test shell for our component we would enter:

$ ember g component-test testable-widget -unit

The setup for our unit test is pretty much the same as our integration test except in the moduleForComponent block we pass unit: true. I am going to set up the unit test to check that a computed property is returning the value that we expect.

test('The abbreviatedContent computed property returns the first 40 characters in the content string.', function(assert) {
  const component        = this.subject();
  const longContentBlock = `Now, all of our tests once again pass.
                            We aren't limited to integration tests
                            when testing components. We can also
                            create a unit test. Let's say we want
                            to add a computed property to our component
                            that returns the first 50 characters of our
                            content property. So meta.`;
  const expectedResult   = `Now, all of our tests once again pass.`;

  component.set('content', longContentBlock);

  assert.deepEqual(component.get('abbreviatedContent').trim(), expectedResult, 'The content is trimmed properly');
});

Now, we just implement our new computed property in the component and our tests will once again pass.

abbreviatedContent: Ember.computed('content', function(){
  const content = this.get('content');
  return content.substring(0, 40);
}),

That is it for this video. Tomorrow we will go over a simple acceptance test. See you then.