Unit Testing Ember.js
Introduction
In my last post, I introduced you to testing JavaScript code with QUnit. Today I’ll be walking you through another example, but this time we’ll be using Ember.js as our framework. We’ll be building a small contact management application from the ground up using TDD along the way. Let’s get started!
New Ember Application
We’ll start by creating a new Ember.js application. We’ll do this using Ember-CLI, the preferred way to start a new Ember application (per the Emberjs.com website, Figure 01)
To get started with Ember-CLI, you’ll need Node.js and NPM installed, it’s as simple as going to the Node website and clicking the big Install button on their homepage. Easy-peasy. Then it’s simply two commands:
Then change into the contacts
directory. Open that folder in an editor of your choice.
Finally, run the command ember serve
to have your app served up on port 4200
. Navigate to http://127.0.0.1:4200, and you should see a simple “Welcome to Ember.js” message.
Running Your Tests
You can either navigate to http://127.0.0.1:4200/tests to see your tests, or you can run the ember test
command. If you want, you can also run a server that will execute your tests, that command is ember test --serve
.
Testing Models
Like my last post where I used TDD to walk you through testing a JavaScript function, we’ll do the same here, but testing Ember. Let’s start by adding a contact
model. We can generate the model and a unit test by using Ember-CLI’s awesome generators:
This generates contact.js
(under app/models
) and a unit test (under tests/unit/models/contact-test.js
). Jump into the test file (tests/unit/models/contact-test.js
) and we’ll start by test driving a computed property on our contact
. We’re going to want a fullName
property on our contact
model. Let’s write a test to get started, ensuring that fullName
computes properly. We also want to make sure that it updates if we change the firstName
or lastName
. Put this at the end of the contact-test.js
file. The test file already has one simple test in it, but the important part of the test is the moduleForModel
call. This is what allows us to test Ember Data models easily.
With the moduleForModel
block in place, we can now write our tests. Be sure to also import Ember from 'ember';
since we’ll be using Ember.run
in our tests. moduleForModel
takes the name of the model, a description, and a object of requirements.
There are 3 assertions in this test. You could break these up into 3 separate tests if you want, but since these are all related, I put them in one test. Most of this should be readable, but the Ember.run
calls might be confusing here. The reasoning for this is that our Ember tests are asynchronous. If we don’t wrap the contact.set
calls, we’ll get an error. When the app is in testing mode, automatic invocation of Ember.run
is disabled. Because of this, anytime we are creating or changing a model with bound or observed properties, we need to wrap them in Ember.run
. Meaning let the run loop complete before working against that property.
The first assertion checks to make sure that fullName
works, given our subject. The second, makes sure that the firstName
triggers a change for the fullName
. The final assertion does the same as the second, but for the lastName
. The code to make this test pass is as follows:
Testing Routes
Let’s now work on a route. We’re going to want to list all the contacts on a route called contacts
. Turning to Ember-CLI’s generators, we can generate a route using the code: ember g route contacts
. This will create three files for us:
- app/routes/contacts.js
- app/templates/contacts.hbs
- tests/unit/routes/contacts-test.js
Since we’re using TDD, hop into the tests/unit/routes/contacts-test.js
file. The initial file contains imports
and the key part of testing routes, moduleFor
.
Like moduleForModel
, moduleFor
takes the name of the module, a description, and a object of requirements. I have added a description of “Contacts Route” to make our tests a little bit friendlier. We can now write a test for our route. We’re using Ember-Data so I know that our route is going to be using the Ember-Data store
. Since we aren’t testing the store
, we’ll mock the object in the test. Jump into tests/unit/routes/contacts-test.js
as we’ll put our tests there
Ember-Data’s find
method returns a promise, so we’ll emulate that behavior in our mock store
. From there we can check the _result
of the model (remember, it’s a promise) and do a deepEqual
assertion to make sure it returns the list we expect.
Finally, here’s the code to make our test pass (in app/routes/contacts.js
).
Testing Controllers
Continuing with our testing, let’s see how we’d test a controller. We’ll use Ember CLI to generate our contacts
controller, using the command: ember g controller contacts
. This generates two files for us.
- app/controllers/contacts.js
- tests/unit/controllers/contacts-test.js
You know the drill by now, we’ll get started in the test file tests/unit/controllers/contacts-test.js
. We want to test that our controller is an ArrayController
.
To start testing a controller, we need to use moduleFor
again.
Now, let’s write our test. We’ll simply test if there is a method addObject
, which only exists on the ArrayController
.
And the simple code to make it pass…
Since we’re testing controllers, let’s look how we’d test an action. We need to create an action to set a flag of whether or not all of the contacts are selected. We’ll put a property on the controller called selectAll
and we’ll create an action called toggleSelectAll
.
We’ll tell QUnit that we expect
three assertions. This is a way to make sure more or less things sneak into your tests. It’s not required, but I figured I show you some additional commands you can use. Then we make sure the default value is false
and once we call toggleSelectAll
, we check to ensure the value is true
. We call the action one more time to make sure it actually toggles the value. Now for the controller code…
Conclusion
Through this post, you learned how to unit test a model, the router, and a controller. In my next post, I’ll show you how to do integration and acceptance testing, where we check the full stack instead of pieces in isolation. Both testing approaches are important to having quality code. I encourage you to check out the official testing documentation as there are more examples to help you.