Getting started with integration testing ember.js using ember-testing and qunit-rails

Ember-testing is a recent addition to the Ember.js framework that allows you to create integration tests for your app using a custom DSL for interacting with your async Ember.js app. We’re going to walk through how to write integration tests with ember-testing using the qunit-rails gem.

Lets all take a moment to thank Erik Bryn  who did the lions share of the work on ember-testing. You can see him talking about it over here with slides and an example app

Lets assume that we have a small library of books and we’ve written an app to keep track of them. The app simply allows us to save some basic details about each book and allocate tags. It didn’t take long to get the app up and running but we’re in the wild west of coding with no tests and the librarian is concerned that the app might have bugs in it. We need to prove it works.

One of the best things about the qunit-rails gem is that you can see the integration tests being run right in front of you. Not only will you see the library app in the bottom right corner of the page but you will see integration tests being run against that instance.  You can see the tests in action below or run them yourself here. Excited yet? I know I was.


To get the started you need to add the qunit-rails to your Gemfile, install your bundle and run the following generator to create the qunit test-helper. You can add a -c param if you’re into coffeescript.

rails g qunit:install
view raw gistfile1.txt hosted with ❤ by GitHub

This will generate a test_helper.js file in /test/javascripts but we are going to replace that default with the following one that I ripped off from Eriks example app

//= require application
//= require_tree .
//= require_self
document.write('<div id="ember-testing-container"><div id="ember-testing"></div></div>');
document.write('<style>#ember-testing-container { position: absolute; background: white; bottom: 0; right: 0; width: 640px; height: 384px; overflow: auto; z-index: 9999; border: 1px solid #ccc; } #ember-testing { zoom: 50%; }</style>');
App.rootElement = '#ember-testing';
function exists(selector) {
return !!find(selector).length;
view raw gistfile1.txt hosted with ❤ by GitHub

This prepares the Ember.js app for testing and renders it into a div in the bottom right corner of the test page as well as declaring a useful helper function to determine whether elements exist in the application.

Lets create very simple test file at /test/javascripts/integration/booksTest.js

module("Ember.js Library", {
setup: function() {, App.advanceReadiness);
teardown: function() {
test("Check HTML is returned", function() {
visit("/").then(function() {
ok(exists("*"), "Found HTML!");
view raw gistfile1.txt hosted with ❤ by GitHub

This declares a module with one test, which loads the root url of the app and checks that something/anything is returned.

If you’re following along you can fire up your rails server now and browse to localhost:3000/qunit . You should see a single test that has been run and your app in the bottom right hand corner.

Thats all the setup done. Now lets build up the suite of integration tests but first lets talk about that simple test. Qunit allows you to define a module which groups similar tests together. At the moment we have one module and one test. The setup and teardown functions are called by qunit before and after EACH test in the module. In the setup function we  tell the Ember.js app to move into a ready state and in the teardown function we reset Ember.App after each test as described here.

The actual test calls visit(“/”) to retrieve the home page and then asserts that the app has indeed returned something, but what are all these functions?

  • The visit function belongs to the ember-testing DSL, It tells the test to load a url in the app. There a few other ember-testing DSL functions…I’ll talk about them later.
  • The ok function is a qunit assertion which checks that the boolean condition passed to it evaluates to true. You can find more qunit assertions here
  • The exists function takes a jQuery selector and returns true if it exists and throws an exception if it doesn’t. We defined it in the test_helper.js class up above

On line 12 in the simple test above we use the visit function to retrieve the root url of the app. This is followed by a call to the then function with an anonymous function describing the test. The visit function is a promise. This means that once the contents of that url have been retrieved or the promise fulfilled, then the anonymous function will be executed. We will continue to use this pattern for the tests.

The Ember-testing DSL currently consists of the following functions:

  • visit (url): This will point the ember application to the specified url
  • click (selector, context): This will click the element described by the jQuery selector. A context for the jQuery selector can also optionally be supplied
  • fillIn (selector, context, text): This will update the value of the element described by the jQuery selector with the supplied text. A context for the selector can be optionally supplied.
  • find (selector, context):  This will retrieve the element identifie by the jQuery selector. A context for the selector can be optionally supplied.

So.. we have an ember-testing DSL for interacting with the app under test and some qunit assertions to check that the behaviour is as expected. Lets write some tests!… We’ll start off with a test to make sure that the contents of the Library is as expected:

test("Check retrieval of books", function() {
visit("/").then(function() {
ok(exists(".navBar"), "The navbar was rendered");
ok(exists("#searchButton"), "SearchButton was found");
visit("/").then(function() {
return click("#searchButton");
}).then(function() {
equal(find(".searchResult").length, 3, "Retrieved correct number of search results");
ok(exists(".searchResult h2:contains('The Great Gatsby')"), "Retrieved title of first book");
ok(exists(".searchResult h2:contains('The Prince')"), "Retrieved title of second book");
ok(exists(".searchResult h2:contains('Slaughterhouse-Five')"), "Retrieved title of third book");
view raw gistfile1.txt hosted with ❤ by GitHub

The first thing we do in this test is we tell qunit to expect 6 assertions. Then we vist the root url / home pag and check that the Nav bar div element is present and that the search button was rendered.

Next up we click the search button and pass in an anonymous function to continue testing the results page once the App is ready. Note how easy it is to deal with th async nature of our Ember.js applications.

We then confirm that the library has three books in it and that the three titles correspond to our seed data, which is really just the set of fixtures being accessed via the ember-data fixture adapter.

So far we’ve used the visit, click and find functions from the ember-testing DSL. Lets write a test to edit the title of a book in the library so that we can use the fillIn function.

test("Check editing of a book updates search results", function() {
visit("/").then(function() {
return click("#searchButton"); //Click the search button
}).then(function() {
function() {
find(".searchResult h2:contains('The Great Modification')") ;
"No existing called the Great Modification"
return click(".searchResult h2:contains('The Great Gatsby') ~ a"); //Select the search result for The Great Gatsby
}).then(function() {
equal($.trim(find(".tags ul li:first").text()), "Fiction", "Found first tag");
equal($.trim(find(".tags ul li:eq(1)").text()), "Classic", "Found second tag");
return click("#editBook"); //Click the search button
}).then(function() {
equal(find(".titleInputBox").val(), "The Great Gatsby", "Found book title input box");
fillIn(".titleInputBox", ".editBook", "The Great Modification");
equal(find(".titleInputBox").val(), "The Great Modification", "Title of book changed on edit page");
return click(".saveEditsToBook"); //Save the changes
}).then(function() {
return click("#searchButton"); //Click the search button
}).then(function() {
ok(exists(".searchResult h2:contains('The Great Modification')"), "Retrieved modified title from search list");
function() {
find(".searchResult h2:contains('The Great Gatsby')") ;
"The Great Gatbsy title has been changed in the search results!"
view raw gistfile1.txt hosted with ❤ by GitHub

In this test, we search for all the books in the library and then check that there is no book called ‘The Great Modification’. We use the qunit throws assertion to ensure that an exception is thrown when we attempt to find a search result with this title.

We then click the link on the book ‘The Great Gatsby’ and load up the show page where we check that the book has the correct tags associated with it and then click the link to edit the book.

Now, on the edit page we check that the book still has the correct title ‘The Great Gatsby’. This is contained in the input box with class ‘titleInputBox’ in the div with class ‘editBook’

We then use the fillIn function to change the value of the input box to the new title ‘The Great Modification’, and for the sake of rigour we validate that this input box now has been indeed been changed to the new title. Happy with this we click the button to save changes to the book and then click the search button again.

Lastly we confirm that ‘The Great Gatsby’ is no longer in the search results, but ‘The Great Modification is.

Success! If you haven’t done so yet, why not try and interact with the application in the bottom right hand corner. You will see the modified book there. The app is fully functional just scaled down in size.

In case you are wondering why the modified title is still there, despite us calling App.reset() in the teardown() function.. it is because only the Ember app is reset. This example has been configured to use the FixtureAdapter from ember-data which is not reset. If you reload the page, the results will revert to the seed defined as fixtures and the tests will pass again.

Ember-data is completely optional for creating ember-testing integration tests. I just found it useful to use the FixtureAdapter to create the seed data. You could use the RestAdapter if you wanted your integration tests to go all the way down to the server and database, but then you would need to come up with a mechanism for ensuring that your data is in a consistent state when you start running the tests.

Hopefully this will get you up and running with ember-testing. Good luck!

The source code for the rails app and tests is available here

This entry was posted in Uncategorized and tagged , , , , , , , , . Bookmark the permalink.

8 Responses to Getting started with integration testing ember.js using ember-testing and qunit-rails

  1. Pingback: Getting started with integration testing ember.js using ember-testing and qunit-rails | Ian Petzer | Dave Laird's Musings

  2. Hi Ian–
    This really helped me get started using ember-testing + qunit.
    I ran into one of the caveats you explicitly mentioned near the end…
    “This example has been configured to use the FixtureAdapter from ember-data which is not reset.”

    Do you (or anyone else) know of a workaround to force ember-data to reload clean fixtures? Currently, without reloading clean fixtures, using FixtureAdapter for tests seems broken. If I update a model in one test, then it will still be modified in the next test. And the test runner runs the tests in a random order. So sometimes they pass, and sometimes they fail.

    If I could force ember-data to reset/reload fixtures in the teardown() after each test, then each test could start from the same clean slate.

    Anyone else run into this issue or found a workaround?

  3. Pingback: Getting started with integration testing ember....

  4. Pingback: Getting started with integration testing ember....

  5. pudgecon says:

    Thanks your post, it helps me a lot!

  6. Pingback: Ember.js: Stuck with initial integration test | BlogoSfera

  7. SD says:

    What about Unit testing?

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s