Unit Testing in Emberjs

I apply the QUnit to my Emberjs project to do unit/integration testing. In unit testing we usually have to mock object/methods to test every condition. I choose SinonJS as my mock library. It’s really easy to use.

Let’s see how to unit testing my components.

Testing html

Create a html that includes qunit,sinon,emberjs and your source codes. Remeber to use debug Emberjs(ember-1.x.x.js) not production(ember-1.x.x.min.js)
Make sure you invoke setupForTesting and injectTestHelpers after Application created.

test.html
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
<head>
  <!-- vendor libs -->
  <script src="js/vendor/jquery-1.9.1.min.js"></script>
  <script src="js/vendor/handlebars-1.0.0.js"></script>
  <script src="lib/ember-1.0.1.js"></script>

  <!-- testing libs -->
  <script src="lib/qunit-1.14.0.js"></script>
  <link rel="stylesheet" href="lib/qunit-1.14.0.css">

  <!-- mock libs -->
  <script src="lib/sinon-1.8.2.js"></script>

  <!-- source codes -->
  <script src="js/components/lobby.component.js"></script>
</head>
<body>
  <div id="application"></div>

  <script>
    var MyApp = Ember.Application.create({
      rootElement: '#application',
    });
    MyApp.setupForTesting();
    MyApp.injectTestHelpers();
  </script>

  <!-- spec codes -->
  <script src="spec/lobby.spec.js"></script>
</body>

Route

I have a lobby route.

LobbyRoute
1
2
3
4
5
MyApp.LobbyRoute = Ember.Route.extend({
  renderTemplate : function(){
    this.render("lobby", { controller: "lobby" } );
  }
});

In QUnit we could use module() to group test cases. test() after module() will be grouped. Then we could add setup and teardown methods. They will be invoked in each test case.

lobby.spec.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module("LobbyRoute", {
  setup: function() {
    MyApp.reset();
  },
  teardown: function() {
    //do something here.
  }
});

test("should render lobby template", function() {
  Ember.run(this,function(){
    route = MyApp.__container__.lookup('route:lobby');

    sinon.spy(route, "render");
    route.renderTemplate();
    ok(route.render.calledOnce, "render should be calledOnce");
  });
});

Above example uses Ember’s __container__.lookup to find specific component and testing it.
But how we make sure the specified method is called? Use sinon.spy to spy a object/method and check it.

Controller

I have a backward function and it will invoke exitApp when isWebview() is true.

LobbyController
1
2
3
4
5
6
7
MyApp.LobbyController = application.AccessibleObjectController.extend({
  backward : function(){
    if( ViewManager.isWebview() ){
      LifeManager.exitApp();
    }
  }
});

In below example, we stub the ViewManager’s isWebview method. I want it returns true during testing. Here is a little difference from previous example. In the last two lines. I have to invoke restore in each stub/spy method. Why? The same method can’t be spy/stub twice. We have to restore it to prevent other test cases be influenced. Why previous example doesn’t have restore in route? Because lobby route is in Ember container and it will be re-created in MyApp.reset()

lobby.spec.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
test("backward should invoke exitApp in webview", function() {
  Ember.run(this, function() {
    controller = MyApp.__container__.lookup('controller:lobby');
    sinon.stub(ViewManager, "isWebview").returns(true);
    sinon.spy(LifeManager, "exitApp");
    controller.backward();

    //assert
    equal(LifeManager.exitApp.callCount, 1, "LifeManager.exitApp should be called");

    ViewManager.isWebview.restore();
    LifeManager.exitApp.restore();
  });
});

View

Have a lobby view and template as below.

LobbyView
1
2
3
4
5
6
7
8
9
10
MyApp.LobbyView = Ember.View.extend({
  templateName : 'tmp_lobby',
  classNames: ['lobby-layout'],
});
Ember.TEMPLATES.tmp_lobby = Ember.Handlebars.compile(
  ['<div class="lobby-header">',
     '<div class="lobby-logo"></div>',
   '</div>',
   '<div class="lobby-content"></div>'
  ].join(""));

In setup we have to append view to document. Then we could use css selector to find expected element. Remeber to remove it from document in teardown.

lobby.spec.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module("LobbyView", {
  setup: function() {
    Gamenow.reset();
    Ember.run(this, function () {
      this.view = MyApp.__container__.lookup('view:lobby');
      this.view.append(); // Hook up to our document.
    });
  },
  teardown: function () {
    Ember.run(this, function () {
      this.view.remove(); // Unhook from our document.
    });
  }
});

test("header should has logo", function () {
  equal(this.view.$(".lobby-header .lobby-logo").length, 1, ".lobby-logo should exist");
});

Comments