Available for Hire!

darthdeus' blog

Random thoughts about the world of programming

Testing Ember.js - Part 1

Ever since I saw the testing slides from EmberCamp I was thinking about testing. Up until now I’ve been using Capybara which is really really really slow.

But @joliss mentioned this thing called Ember.testing which should automagically fix all of the async problems which make tests ugly, such as waiting for the application to initialize and finish routing.

In its essence Ember.testing = true disables the automatic runloop, which gives you the control to manually schedule asynchronous operations to happen in a one-off runloop via Ember.run.

Ember.run will run the given function inside a runloop and flush all of the bindings before it finishes, which means you can render a view inside Ember.run and check the DOM right after that. Here’s an example from the Ember.View tests

1
2
3
4
5
6
7
8
9
10
11
12
13
view = Ember.ContainerView.create({
  childViews: ["child"],

  child: Ember.View.create({
    tagName: 'aside'
  })
});

Ember.run(function(){
  view.createElement();
});

equal(view.$('aside').length, 1);

As you can see the view.createElement() happens inside the runloop scheduled by Ember.run which will return only after the view was completely rendered and all bindings flushed.

Let’s take a look at a complete example and take it apart step by step

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Testing mode disables automatic runloop
Ember.testing = true;

// Creating an application normally happens async,
// which is why we have to wrap it in Ember.run
Ember.run(function() {
  App = Ember.Application.create();
});

App.Router.map(function() {
  this.route("home", { path: "/" });
});

App.Store = DS.Store.extend({
  revision: 11,
  adapter: DS.FixtureAdapter.extend({
    // This will make the FixtureAdapter do everything synchronously
    // instead of using setTimeout, which is vital because setTimeout
    // happens outside of the runloop.
    simulateRemoteResponse: false
  })
});

App.User = DS.Model.extend({ name: DS.attr("string")});
App.User.FIXTURES = [ { id: 1, name: "brohuda" }];

App.HomeRoute = Ember.Route.extend({
  model: function() {
    return App.User.find(1);
  }
});

// Enabling Ember.testing will also disable automatic initialization,
// which forces us to initialize manually
Ember.run(function() {
  App.initialize();
});

// In real life this would be an assertion,
// here we'll just check if everything is rendered at this point in time.
$("p strong").append($("h2").text());

Take the example apart, play with it and try to figure out what works and what doesn’t :)

If you see

1
2
assertion failed: You have turned on testing mode, which disabled the run-loop's autorun.
You will need to wrap any code with asynchronous side-effects in an Ember.run

it means that you forgot to wrap something in Ember.run. I hope this is a good enough introduction. In one of the upcoming articles we’ll take a look at simple Ember application and try testing it with a full featured testing framework.