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.
Comments for Unit and Integration Tests