Unit test your AngularJS views

Published: 2014-01-01 by Lars  testingUI

Most JavaScript-based Model-View-Controller frameworks make it very easy to write unit tests for the Model, and reasonably easy to write unit tests for the Controller. However, writing unit tests for the View is often seen as more difficult. This is no different with AngularJS, where most guidelines argue for end-to-end testing to get good test coverage for your views. However, end-to-end testing is slow and fragile compared to unit testing, and therefore doesn't scale well to large applications. In this blog post I will show how you can write fast unit tests for your AngularJS views using Jasmine.

Let's look at a simple example:

<div>
    <div>I'm Here</div>
    <button>Toggle</button>
</div>

We are using two built-in AngularJS directives: ng-show controls the visibility of the div-element via the scope variable isOn. ng-click manipulates the value of that scope-variable whenever the button is clicked. The ng-click code would normally go into a controller, but is kept here for demonstration purposes.

We want to write unit tests for this view, to ensure that we have used the correct directives and configured them correctly. Assuming we had a way to load the view and compile it with AngularJS, our test cases could be written like this:

it('should connect the div', function () {
    var div = formElement.find('div');
    expect(div.css('display')).toBe('none');
});

it('should connect the button with the div', function () {
    var button = formElement.find('button');
    button.trigger('click');

    var div = formElement.find('div');
    expect(div.css('display')).toBe('');
});

Fortunately, AngularJS exposes the $compile service that allows us to create a DOM-node and have Angular connect the directives and scope. This process is quite similar to how you would setup an HTML fixture for testing your controller:

beforeEach(inject(function(_$compile_, _$rootScope_) {
    $compile = _$compile_;
    $rootScope = _$rootScope_;
    $rootScope.isOn = false;
    formElement = angular.element(viewHtml);
    var element = $compile(formElement)($rootScope);
    $rootScope.$digest();
}));

Now we just need to load the view.html file. We can use a synchronous $.ajax() call and to ensure that we only load the file once, we can leverage the AngularJS template cache:

beforeEach(inject(function($templateCache) {
    viewHtml = $templateCache.get('src/view.html');
    if(!viewHtml) {
        viewHtml = $.ajax('src/view.html', {async: false}).responseText;
        $templateCache.put('src/view.html', viewHtml);
    }
}));

You can now enjoy fast unit tests of your AngularJS views! A working sample project can be found at https://github.com/larsthorup/angular-view.

Discuss on Twitter