A Primer to Unit Testing for EmberJS

As I’ve been learning Ember.js, I’ve been also learning about its unit testing approaches.   I’ve previously done TDD for UIs in Java, and then some in Jasmine as I’ve been trying to get back into JavaScript.  Ember’s default approach is slightly different, but I like it.  Here are some basics I went through to get a basic unit test environment up, enabling the development team I work on to test drive development in Ember.

QUnit

When you use Ember-CLI out of the box, it gives you a unit test and integration test setup around QUnit. QUnit is a simple, yet solid unit test framework, used and developed by the JQuery team.  Part of the QUnit setup gives you both a phantom JS and a Chrome test runner using Testem.  This allows you to run your tests either browserless by running “ember test”, or run both by giving the —server argument.   The great thing about running with the server argument is that testem will monitor your files and rerun the tests whenever it detects a change.

Ember QUnit Helpers

http://guides.emberjs.com/v1.10.0/testing/unit-test-helpers/ Ember provides several out of the box helpers that aid in unit testing Ember-specific objects.  Using the moduleFor helper method will load the appropriate ember object by name and prepare it for unit testing.  It also provides some conveinences around async testing that we’ll talk about next.  Running the implicit “subject” function will instantiate and initialize the given ember object under test, while the render object will attach it to the dom.  Once in the dom you have access to the JQuery object instance to exercise and run assertions on its state.

import { test, moduleForComponent } from 'ember-qunit';  

moduleForComponent('search-pane', {  
    needs: ['component:chat-search-result']  
});  

test('it should hide an element on the component when myAction is sent', function(assert) {  

    component = this.subject();  

    component.send('myAction');  

    assert.ok(this.$().find('.myComponentClass').hasClass('hide'));  

});  

Async considerations

Much of the flows in Ember (and web in general), have asynchronous aspects. Calls make ajax requests, or sometimes will use other asynchronous functions such as debounce, which will prevent a function from being called multiple times over a given time period.  You’ll often find that in your tests your assertions will be called before the actual function is finished executing.  Instead of forcing you to do things like setTimeout to wait to run assertions, which can be either unreliable or slow, Ember allows you to return an ember Promise from your testcase.  This promise can either be from the code (if your code being tested returns a promise, or a promise you create.  The below code returns a promise that uses the ember observer functionality to wait to run the assertions until the value we are asserting on is updated.

test('it should do blah blah blah in an asynchronous call', function(assert) {  
    //setup test....  

    var controller = this.subject();  
    controller.send('loadHistoricChat', myArg);  
    new Promise(function(resolve) {  
        controller.addObserver('historyRoom', function() {  
            resolve();  
        }  
    }.then(function() {  
        //run my asserts...  
    };  
});  

Spies, Stubs, and Mocks with ember-sinon

https://github.com/csantero/ember-sinon Ember has an add-on that will install the sinon.js library that gives you a nice api capable of mocking, spying and stubbing.  The spy object allows you to pass a function to your class under test and run assertions on how the class interacts with it. The stub object provides an easy way to fake out dependencies.  The mock objects let you specify expectations on how spies should be used, failing the test if not used as expected.  The below code builds on the previous example and creates a fake service object, where we are stubbing a couple of methods and asserting spies for others.

test('it should fetch historical messages based on a given chat search result', function(assert) {  
    messages = [  
        Ember.Object.create({body: "yo", name: "todd", date: "today"}),  
        Ember.Object.create({body: "bro", name: "jo", date: "then"})  
    ]  
    activeRoom = {  
        jid: 'abc'  
    }  
    historyRoom = {  
        addHistoryMessages: sinon.spy()  
    }  
    chatService = Ember.Object.create({  
        requestHistory: sinon.stub().returns(new Promise(function(resolve){resolve(messages)}),  
        activeRoom: activeRoom,  
        newRoom: sinon.stub().returns(historyRoom)  
    });  
    controller = this.subject({  
        chatService: chatService  
    });  

    controller.send('loadHistoricChat', messages[0]);  

    new Promise(function(resolve) {  
        controller.addObserver('historyRoom', function() {  
            resolve();  
        }  
    }).then(function() {  
        assert.ok(chatService.requestHistory.calledWith(sinon.match({  
            jid: 'abc'  
        })));  
        assert.ok(historyRoom.addHistoryMessages.calledWith(messages));  
    };  
});  

Stubbing Ajax Calls

Often it is useful to test the interactions between your Ember components and the model, especially when you use the ember ember-data api.  In these cases you might want to simulate server responses from ajax calls.  There are a few options here.  If you want a quick method of mocking ajax for unit tests I’d recommend something like mockjax.  Mockjax provides a simple api for providing fake answers to specified jquery ajax calls.  Sinon also has a similar mechanism through its FakeXMLHttpRequest.  If you need something more elaborate for doing things like integration tests, I’d recommend ember-mirage.  Mirage is an ember add-on that provides a richly featured server stubbing library that runs client side.
Below is an example of a mockjax call to stub an http get.  Making this call will intercept the jquery xmlhttprequest and return the stubbed json:

$.mockjax({  
    url: '/api/v2/search',  
    contentType: 'application/json',  
    status: 200,  
    responseText: {  
        res: results  //variable holding json response  
    }  
});  

Code Coverage with Ember Blanket

https://github.com/sglanzer/ember-cli-blanket The ember blanket add-on will add blanket.js execution to your tests, driven through the testem UI.  This will allow the developer to run tests with coverage and view the report.

Ember Pre-Push Hooks

https://www.npmjs.com/package/prepush-hook If you want to add some added enforcement to keep your git remote branches clean, you can add the git pre-push hooks library to your project. It provides an easy way to run your linting and test cases when you perform a git push, failing the push if any testcase or lint fails.