Ember: Tested

If you’ve been following along with my last couple of posts, welcome back. If not, I’d recommend you check out JavaScript Testing 101. You should especially read Unit Testing Ember.js since we’ll be continuing to build off the app we started in that post. In that post, we created a new Ember-CLI application and started unit testing our models (of which we only have one, contact), our controllers (where we have one, ContactsController), and our route (again, we have one, contacts). I have posted the complete code on GitHub for your convenience.

Integration != Acceptance Testing

Some people use the terms integration and acceptance testing interchangeably, but they are wrong. While the tests may look similar, that does not make them the same. Integration tests are tests between systems, ensuring that everything works in harmony. On the other hand, acceptance tests, are written from the point of view of a user. For example, we might write an integration test to verify that we can pull a list of contacts from the API. While if it were an acceptance test, we would test whether or not a list of contacts appear on a page. In this case, often, they may result in testing the same things, but from a different point of view. I just wanted to clarify the differences in case you were not aware of them. We are going to focus acceptance tests in this post because we want to focus on user interactions, and our tests will test the application end-to-end.

Introducing FactoryGuy

Feel the thrill and enjoyment of testing when using Factories instead of Fixtures. Factories simplify the process of testing, making you more efficient and your tests more readable.

FactoryGuy lets us use factories instead of fixtures in our tests. Factories handle associations better. Also, FactoryGuy allows us to use either the RESTAdapter or ActiveModelAdapter, which our production app would be using, instead of the FixtureAdapter. I am not going to bore you with all the differences between factories vs. fixtures as you’ll find plenty of articles on the web related to the topic. Many articles that you’ll find are typically for Ruby on Rails, but the same principles apply to Ember. To quickly summarize, a fixture allows you to have one “set in stone” record, where with a factory, you can have n distinct records.

Setting up FactoryGuy in Ember-CLI is a snap thanks to @cristinawithout. Head over to her GitHub repo and you’ll find the instructions for setting it up (repeated here). Simply use npm to install the dependency: npm install --save-dev ember-cli-data-factory-guy. Then run ember g ember-cli-data-factory-guy to install the bower dependency.

Defining a Factory

In order to use FactoryGuy, we first need to create a factory. A factory is simply a template that FactoryGuy uses when generating the model that we want. Since our app only has one model, we’ll create a single factory, named contact.js in a new folder, /test/factories. Our contact factory code we will use FactoryGuy’s ability to use sequences for our firstName attribute, this ensures we do not have duplicate user names. So instead of User Foo for all our contact names, we’ll get User1 Foo, User2 Foo, and so on. Let’s define our factory now.

import FactoryGuy from 'factory-guy';

FactoryGuy.define('contact', {
  sequences: {
    name: function(num) {
      return 'User' + num;
    }
  },

  default: {
    firstName: FactoryGuy.generate('name'),
    lastName: 'Foo'
  }
});

export default {};

In our integration/acceptance tests, we’ll have to import this (e.g. import contactFactory from '../factories/contact';) anytime we need to use the factory.

Using Our Factory

We’ll start by generating an acceptance test for our contacts page. Remember we are doing everything test-first, so we do not have a contacts page yet, just our route for the contacts:

Router.map(function() {
  this.route('contacts');
});

If you try to hit that with your browser now, you’ll see that you get two errors. A 404 error when it tries to find the contacts (because we have no API endpoint), and an Ember error, Error while processing route: contact. With this in mind, let’s see what happens when we generate our acceptance test. Using Ember-CLI, we can use the generate command to stub our test out for us.

ember g acceptance-test contacts

If you run the test; I have ember test --serve running, so my tests run automatically, you’ll see that we have a failing test. Hop over to tests/acceptance/contacts-test.js and let’s take a look.

test('visiting /contacts', function(assert) {
  visit('/contacts');

  andThen(function() {
    assert.equal(currentPath(), 'contacts');
  });
});

It is attempting to visit the contacts route like we did, and it is encountering the same problems that we had (no API == 404 & Ember cannot process the route). We’ll remedy this situation with FactoryGuy.

Fixing Our Error

The first thing we need to do is import FactoryGuy and import our factory. At the top of the file, add these two lines:

import { testMixin as FactoryGuyTestMixin } from 'factory-guy';
import contactFactory from '../factories/contact';

Now, we have to do a little setup before we can use the testMixin. After the var application; line, add the following:

var TestHelper = Ember.Object.createWithMixins(FactoryGuyTestMixin);
var testHelper;

Once we have those variables defined, we are going to do a little work with the beforeEach and afterEach. We’ll inject the things we need for FactoryGuy. The code below is what the module looks like after the changes for FactoryGuy.

What’s a module? A module is a part of QUnit that defines a group of tests together. It also has hooks to run code before and after each test.

module('Acceptance: Contacts', {
  beforeEach: function() {
    application = startApp();
    testHelper = TestHelper.setup(application); // Needed to use FactoryGuy
  },

  afterEach: function() {
    Ember.run(function() { testHelper.teardown(); }); // Clean up the FactoryGuy testHelper
    Ember.run(application, 'destroy');
  }
});

In the beforeEach, we set up a test helper that we’ll use in our tests. In the afterEach we teardown the testHelper before we destroy the application. Now we can fix our failing test with FactoryGuy.

Our failing test simply tries to visit the /contacts page.

test('visiting /contacts', function(assert) {
  visit('/contacts');

  andThen(function() {
    assert.equal(currentPath(), 'contacts');
  });
});

What’s with the andThen? Tests in QUnit/Ember are asynchronous. The andThen ensures that we’ve completed visiting the contacts route before running our assertions.

In order to fix our tests, we will need to mock the ember-data call, so it does not try to go out to the server (where we do not have an API). Using FactoryGuy makes this ultra simple. We just need one line in order to mock contacts being returned. Right before the visit, we’ll tell FactoryGuy that we want two contacts. The number here doesn’t matter, now our test starts like so:

test('visiting /contacts', function(assert) {
  testHelper.handleFindAll('contact', 2);
  visit('/contacts');
  ...

Now we have a passing test. Isn’t that awesome? FactoryGuy is an excellent tool to use when working with tests and ember-data. If you’ve used other mocking frameworks, you can truly appreciate how easy it is with FactoryGuy.

So we have our first test down, let’s move on to something a little more complex. Let’s display our contacts on a page. In our test, we’ll navigate to the page and then we’ll check that each contact is wrapped in an element with the class contact.

test('contacts should be listed on the page', function(assert) {
  assert.expect(1); // Ensure there is only one assertion
  testHelper.handleFindAll('contact', 2);
  visit('/contacts');

  andThen(function() {
    assert.equal(find('.contact').length, 2);
  });
});

Now, in app/templates/contact.hbs, we will write the Handlebars to make the test pass:

{{#each contact in model}}
  <div class="contact"></div>
{{/each}}

Looking at this code, you probably think that the Handlebars code is not complete. Well, what are we testing? We are simply checking for two elements with the class contact. We did not specify anything about displaying the contact’s name or anything. This is an important lesson. Only write the minimum amount of code to make the test pass. Let’s correct this with another test. This time we’ll create a user with a known name and make sure that they are displayed on the page. First, we’ll have to import FactoryGuy from 'factory-guy', and then we’ll use FactoryGuy.make to make a user where we specify the name for the user. We’ll use this as part of our handleFindAll call. Here’s the new test.

test('contacts should have their name listed on the page', function(assert) {
  assert.expect(1);
  var contact = FactoryGuy.make('contact', {firstName: 'Damien', lastName: 'White'});
  testHelper.handleFindAll('contact', 1, [contact]);
  visit('/contacts');

  andThen(function() {
    assert.equal(find('.contact:first').text(), 'Damien White');
  });
});

Now we are testing to make sure that our .contact element has the correct text. The revised Handlebars template, now references the contact’s fullName property.

{{#each contact in model}}
  <div class="contact">{{contact.fullName}}</div>
{{/each}}

Conclusion

We just tapped the surface of acceptance testing, but this should get you going on the path of testing your Ember applications. This post combined with the other posts (JavaScript Testing 101 and Unit Testing Ember.js) should set you up to completely test your Ember JS applications.