Discover Ember

Back to All Courses

Lesson 17

Automated testing

Why use testing?

Writing automated tests for your app is a part that is often overlooked by developers but incredibly handy once you understand how it works.

To understand its utility, think about how you normally test your apps today. Do you just click around the page for a few minutes before every deploy hoping to catch a bug before your users do? That might work, but it's prone to human error, plus, it does get a little repetitive right?

Also, how many times have you fixed one bug just to realize that your "fix" actually broke another part of the site? For larger apps, it happens all the time!

The concept of automated testing is simply to let robots handle the testing process for you. Your only job is then to write good test cases.

In Ember, every time your app is compiled, the "test robots" will run through all the test cases (like logging in, creating a post... etc) and you'll get instant feedback if something isn't working as it should because of your changes. This allows you to identify bugs quickly and efficiently.

Ember uses a testing library called QUnit, which is maintained by the jQuery team. If you're not familiar with QUnit, you might want to read through its website quickly just to get familiar with it, but don't worry, it's quite simple really.

In Ember, there are 3 types of tests:

  1. Unit tests: for testing small, isolated pieces of code. (e.g. check if a model's computed property returns the values we expect it to)

  2. Acceptance tests: for testing complete user interactions, going from page to page... etc. (e.g. check if the website's signup process behaves as expected)

  3. Integration tests: this is a fairly new category that's bigger than a unit test, but smaller than an acceptance test. You can manipulate the DOM elements like in a real browser, but it's still limited to isolated parts of the page. (e.g. check if a component renders and see if it changes when the user interacts with it)

Integration tests

Did you notice that each time you generated a component in the previous chapters, a corresponding test file was created automatically? Those are integration tests.

Let's see what kind of test Ember has generated for our compose-modal component by opening the file tests/integration/components/compose-modal/component-test.js:

// tests/integration/components/compose-modal/component-test.js

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

moduleForComponent('compose-modal', 'Integration | Component | compose modal', {
  // Here we can specify other units that are required for this test
  integration: true
});

test('it renders', function(assert) {
  assert.expect(2); // We expect 2 assertions in this test

  // Set any properties with this.set('myProperty', 'value');
  // Handle any actions with this.on('myAction', function(val) { ... });

  this.render(hbs`{{compose-modal}}`); // We render the component

  // The text in the component should be an empty string (pointless in this case)
  assert.equal(this.$().text().trim(), '');

  // Template block usage:
  // (We are not actually using this)
  this.render(hbs`
    {{#compose-modal}}
      template block text
    {{/compose-modal}}
  `);

  // Check that block usage works correctly with text as well
  assert.equal(this.$().text().trim(), 'template block text');
});

As you can see, the file contains only one test called "it renders", which checks that the component is created and gets added to the DOM without any problems. This test is automatically added to every component you generate through Ember CLI.

Now, if you visit http://localhost:4200/tests, you can see a list of all the automated tests that Ember has created for you. You can also filter the list down to just this particular component by searching for "compose modal" on the top right of the page.

Oh no! Our test is failing!

Now don't worry about the fact that the test is currently failing. This is normal. Although Ember makes many good decisions, it cannot always get things right, and in this case, the assertions are not well-suited for our component (here it checks for a text string inside the component, when in fact it contains HTML).

Let's change the assertions into something that actually makes more sense! For example, we could test if the component contains a chirp-button and a textarea (we'll assume that that's enough of a sign that the contents of the component have rendered correctly). Notice that QUnit mostly uses standard jQuery syntax in its assertions.

// tests/integration/components/compose-modal/component-test.js

test('it renders', function(assert) {
  assert.expect(2);

  // Set any properties with this.set('myProperty', 'value');
  // Handle any actions with this.on('myAction', function(val) { ... });

  this.render(hbs`{{compose-modal}}`);

  // Check if the rendered component contains exactly one button with the class "chirp"
  assert.equal(this.$().find('button.chirp').length, '1');

  // Check if the rendered component contains one textarea
  assert.equal(this.$().find('textarea').length, '1');
});

After saving the changes, the test page should reload automatically, and tada! The test should now pass!

1. and 2. correspond to the two assertions that the test makes.

This is mostly just to show that Ember doesn't always get things right, and that you shouldn't panic if you see lots of red on your test page initially. Just stay calm and fix the tests Ember got wrong (and if it got things right, well fix your code)!

Unit tests

Now let's look at unit tests. Unit test files are automatically generated every time you create a model or a route through the CLI.

I personally don't use unit tests in Ember that often to be honest, since the Ember library already does a pretty good at catching errors on models and routes by itself. So in the Chirper app this particular test might look a little silly, but heck, it's always good to practice for upcoming unit tests that you might need in the future.

Let's open up the file tests/unit/chirp/model-test.js and add a new test there. As you can see, it already contains one test called it exists which simply makes sure that the model file hasn't been deleted or anything.

The test we're going to write is going to check if the model's user-key correctly maps to the user-model with a belongsTo-relationship. To do that, we first of all need to import the Ember library at the top of the file, as well as specify that we need to import the user -model. The first lines in your file should look like this:

// tests/unit/chirp/model-test.js

import { moduleForModel, test } from 'ember-qunit';
import Ember from 'ember'; // <-- Add this!

moduleForModel('chirp', 'Unit | Model | chirp', {
  needs: ['model:user'] // <-- ...and this!
});

Next, we define our test right after the one called it exists:

test('user relationship', function(assert) {
  // Get the chirp model
  var Chirp = this.store().modelFor('chirp');

  // Get its 'user'-key
  var relationship = Ember.get(Chirp, 'relationshipsByName').get('user');

  // Make sure that the relationship works as expected
  assert.equal(relationship.key, 'user');
  assert.equal(relationship.kind, 'belongsTo');
});

Now go back to localhost:4200/tests and search for "user relationship" and you should see the test pass:

Again, this might not seem super useful, but now you've got a taste of how it works.

One thing that might actually be very handy to test in models is computed properties. We don't have so many of them in this particular app's models, but if we did, we could supply our model with some arbitrary data and then check if the computed properties transformed that data in the way we expected.

User interaction in an integration test

Let's look more closely at how we can test user interactions in our integration tests!

If you go back to how we built our compose-modal component, you might remember that one of the features we added was that, if the text you compose in the textarea is longer than 140 characters, the "character count"-text should turn red. This is a typical example of something that we might want to test!

The way we'll test it is simply by setting the chirpText variable to a string longer than 140 characters, and after that, check if the DOM element p.remaining-chars has a warning-class (which it should have).

Head back to the component-test.js-file that we opened earlier, and add this chunk of code after the first test:

// tests/integration/components/compose-modal/component-test.js

test('long text makes label red', function(assert) {
  var shortText = "Hello world!";

  this.set('chirpText', shortText);
  this.render(hbs`{{compose-modal chirpText=chirpText}}`);

  assert.ok(this.$().find('p.remaining-chars').hasClass('warning'));
});

As you can see, we set the text to "Hello world!"

Next, we render the component with that property and check if the p.remaining-charselement has a warning-class.

assert.ok is one of the simplest QUnit functions: it only checks if what's inside it is true. Since "Hello world!" is less than 140 characters, this test should therefore fail. Let's see if that's the case by going back to http://localhost:4200/tests:

As you can see, the test fails, which is good since that's the expected behaviour if our text is under 140 characters!

Now let's change "Hello world" into something longer instead and see if the test passes. Replace the first two lines in your test:

var longText = 'Lorem ipsum dolor sit amet, eam ex cibo elitr tamquam. Nusquam adipiscing ea sea, habemus minimum vis cu. Pri ponderum percipitur ex, eu mei tamquam eloquentiam. Ius apeirian insolens ea.';

this.set('chirpText', longText);

Hooray! Our test passes!

That's the basics of testing the user's interaction with your components. It might seem tedious to write all these tests at first, but trust me, you'll be happy you did in the long run, especially if you have multiple people working on your project (and you need to blame whoever broke the build)!

Acceptance tests

Once again, we saved the best for last. Contrary to unit and integration tests which only test an isolated part of your app, an acceptance test will boot the entire app in a virtual browser and test certain user workflows to check if everything works well together. It's basically like a simulated super-user.

One of the things we might want to test the workflow of is the login process. Basically we want to test two things:

1. The happy path:

  1. The user visits the index page

  2. They type in their username and password in the input fields

  3. After clicking on "Log in", they should be redirected to the Home-route

2. The sad path:

  1. The user visits the index page

  2. They type in the wrong credentials in the input fields

  3. After clicking on "Log in", they should see an error box

Let's implement these tests! We'll start by generating the file through the CLI:

ember g acceptance-test user-can-log-in
// tests/acceptance/user-can-log-in-test.js

import Ember from 'ember';
import { module, test } from 'qunit';
import startApp from 'chirper/tests/helpers/start-app';

module('Acceptance | user can log in', {
  beforeEach: function() {
    this.application = startApp();
  },

  afterEach: function() {
    Ember.run(this.application, 'destroy');
  }
});

// Happy path
// (Make sure you use a valid username and password!)
test('User can log in', function(assert) {
  visit('/');
  fillIn('.login-box input[type="text"]', 't4t5');
  fillIn('.login-box input[type="password"]', 'mypassword');
  click('button.login');

  andThen(function() {
    // Check if we've been redirected:
    assert.equal(currentRouteName(), 'home');
  });
});


// Sad path
test('Wrong credentials shows error box', function(assert) {
  visit('/');
  fillIn('.login-box input[type="text"]', 't4t5');
  fillIn('.login-box input[type="password"]', 'wrongpassword');
  click('button.login');

  andThen(function() {
    assert.equal(currentRouteName(), 'index');
    // Check if the error box element exists:
    assert.ok($('p.error').length !== 0);
  });
});

As you can see, Ember comes with some awesome built-in helpers like visit() ,fillIn() , click() and currentRouteName() to make acceptance tests easy!

Now go back to http://localhost:4200/tests and search for your test ("user can log in") to see something really cool!

Woah, did you see that? You get a glimpse of the robot working its way through the app as fast as possible!

The tests pass, which means our login flow works as expected! Awesome!

This is an introduction to how testing in Ember works. If you thought this was cool, there's still a lot to learn, especially when it comes to the concept of Test-Driven Development, which you can read more about here!