Unit Testing AngularJS Controllers, Views, and More: Part One

AngularJS makes a lot of things easy, but one thing you still have to come to grips to, is deciding:

  • What to unit test.
  • How to unit test it.
  • How to unit test it easily.

I’m not saying it’s hard, but it can sometimes be convoluted. Let’s answer the first question.

What to unit test

Normally, we want to be unit testing logic. This would usually translate to unit testing controllers. However, because AngularJS allows us to embed expressions inside our templates, it’d be nice to be able to unit test template logic too. To this, people might object because, of course, Selenium (for example) would be the choice to perform end-to-end testing. Which I agree to completely, except that end-to-end testing is not synonymous with controller-view integration testing.

In a past life, when I was working with Silverlight, using MVVM (Model, View, View-Model), my prevailing philosophy was to move most (if not all) the view related logic to the view model, so that we could unit test the view model thoroughly. The only remaining bugs would be a faulty binding. As it turns out, using this philosophy is quite useful in AngularJS as well. However, I do want to go a step further to be able to unit test these bindings as well. I also would want to be able to test the state of HTML elements. Normally, a higher-level acceptance test would do this, but the higher up the testing abstraction one moves to, the harder it is to pinpoint low-level issues when they arise.

Let’s go one step further. I’d also like to be able to unit-test interactions. This basically means verifying what would happen in a controller/view if the user interacted with a certain UI element.

So, in short, what I want to be able to test are:

  • Controllers
  • Template-Controller binding
  • View interactions

In this series, I shall start with a simple project structure, and use Duck-Angular, a framework I’ve developed to show how you can do all of the above. I’ll walk you through the code of Duck; hopefully, this will serve to illustrate the design thinking behind AngularJS too.

What is Duck-Angular?

Duck-Angular is a container for bootstrapping and testing AngularJS views and controllers in memory. No browser or external process is needed. Duck-angular is available as a Bower package. Install it using bower ‘install duck-angular’.

Include it using RequireJS’ define(). Your controller/service/object initialisation scripts need to have run before you use Duck-Angular. Put them in script tags, or load them using a script loader like RequireJS or Inject.

The Github repository is here. As we go on, we’ll see how Duck-Angular works, and how you can use it in your unit tests.

How to unit test it

Say whatever you will about AngularJS, it is very hackable. Some of that can be attributable to the Javascript language itself, but there are some very good design decisions that the development team has taken. The two points which come to mind are:

  • Copious event publishing
  • Unintrusive dependency injection model

As we shall see, each of these points make it possible to write unit tests for almost every part of your application code.

Which Libraries?

Before beginning, I shall list out the libraries I’ve used for this example project:

  • Duck-Angular (A container for bootstrapping and testing AngularJS views and controllers in memory: no browser or external process needed)
  • RequireJS (Isolates libraries, including AngularJS for your specific context)
  • Q (Used for bootstrapping the Angular application, and mocking $q)
  • Mocha and Chai (Unit testing framework, and assertion framework, respectively)
  • Sinon (Mocking/Stubbing framework)
  • Mocha-as-Promised and Chai-as-Promised (Extensions to Mocha and Chai to ease handling of promises)

The example project to follow along is AngularJS-RequireJS-Seed Project. It is in a bit of a flux at the moment, but we’ll fix that soon.

Project Structure

With the imminent advent of the ES6 Module System, it only makes sense to structure Javascript code as proper, first-class application code, instead of a bunch of script tags. To this end, I’ve used RequireJS to structure the project. There are several other beneficial effects of using RequireJS apart from this isolation, which we’ll soon see. The folder structure of the app is as below:

js
 └───public
     ├───js
     │   ├───app
     │   │   ├───controller
     │   │   ├───directive
     │   │   ├───factory
     │   │   └───services
     │   ├───lib
     │   └───test
     │       ├───lib
     │       └───unit
     │           ├───controllers
     │           ├───services
     │           └───ui
     └───templates

Some quick notes about the folder structure.

  • The test/lib directory contains test-specific libraries like Mocha, Chai, etc.
  • This folder structure is not an absolute must; it just makes it easier to explain the application organisation.

Apache Server Configuration

The choice of server to serve these static assets is up to you, but just in case you’re using Apache, here’s the snippet from httpd.conf that you might find useful. Of course, you’ll have to modify the local directory path to your JS project.

    ProxyPreserveHost On
    ProxyRequests Off
    ProxyPass /example/static !
    Alias /example/static/ "C:/projects/AngularJS-RequireJS-Seed/example-app/src/main/resources/public/"

    Header set Cache-control "no-cache,must-revalidate"
    Header set pragma "no-cache"
    Header set Expires "Sat, 1 Jan 2000 00:00:00 GMT"

    <Directory "C:/projects/AngularJS-RequireJS-Seed/example-app/src/main/resources/public">
       Options Indexes FollowSymLinks MultiViews ExecCGI
       AllowOverride All
       Order allow,deny
       Allow from all
    </Directory>

Bootstrapping the application

The relevant snippet which starts off the app is in index.html:

<head>
  <title>RequireJS+Angular</title>
    <script src="/example/static/js/lib/require.js" data-main="/example/static/js/app/bootstrap.js"></script>
</head>

Looking at the code above, we see that the first Javascript file to be loaded and run, is bootstrap.js. The bootstrap.js code is very short, and is shown below.

    require(["app.config"], function(config) {
    require(["app/app"], function(app) {
    app.bootstrap(app.init());
    });
});

This code loads up two files, app.config.js, and app.js in sequence (They should not be loaded parallely, hence the nested require’s). Once the app module has been loaded, it is initialised (init()), and bootstrapped (bootstrap()). The two phases are separate so as to allow the developer to add extra test-specific initialisation after init(), but before bootstrap().

Now, the question is: what do bootstrap() and init() do? Here is the init() function from the app.js module:

var init = function() {
  var app = angular.module('ExampleApp', []);
  services.init(app);
  libs.init(app);
  directives.init(app);
  controllers.init(app);
  factories.init(app);
  app.config(['$routeProvider', function ($routeProvider) {
    $routeProvider.
      when('/navigation', {templateUrl: '/example/static/templates/navigation.html', controller: 'navigationController'}).
      when('/route1', {templateUrl: '/example/static/templates/route1.html', controller: 'route1Controller'}).
      when('/route2', {templateUrl: '/example/static/templates/route2.html', controller: 'route2Controller'}).
      otherwise({redirectTo: '/navigation'});
  }]);
  return app;
};

Parts of this may be familiar to you, for example, the wiring up of the routes. This is where we manually bootstrap the application, calling it “ExampleApp”. To set up controllers, services, factories, etc., I follow a process which might seem a little unfamiliar. Not to worry, it’s pretty simple. The core idea here, which will resurface when we actually get down to testing the entities, is that every Angular controller, factory, service, etc. starts off as a RequireJS module.

Make every Angular entity start off as a RequireJS module. Why?

Why is this important? Think of any AngularJS controller. You declare it like so:

    angular.controller("SomeController", function(...) {
       // Controller code
}

Now, let’s pause for a second, and think of how we can get a hold of the instance of that controller if we ever wanted to unit test it? Well, it is an AngularJS controller, so you’d need to be able to ask AngularJS to instantiate it, using AngularJS’ dependency injection framework (in this case, we’d use $injector.get(), or the $controller service).

This implies that you will need to at least initialise your AngularJS app before you can use a controller object. Kind of sucks, huh? But, if the controller’s constructor function started out as a RequireJS module, you’ll not need to bootstrap your app to create a controller. All you really do is call a constructor function, and you’re done. Not only that, you have full control over what dependencies you inject inside your controller, because you’re invoking the constructor function yourself. Later on, we’ll see how to specify only a subset of a controller’s dependencies, while the rest are populated by their default (production) dependencies.

So, to register controllers with AngularJS, controllers.js does this:

  define(["route1-controller",
  "route2-controller",
  "navigation-controller"
], function (route1Ctrl, route2Ctrl, navigationCtrl) {
    var init = function (app) {
      app.controller('route1Controller', ['$scope', 'service1', '$q', route1Ctrl]);
      app.controller('route2Controller', ['$scope', '$location', '$q', 'service2', route2Ctrl]);
      app.controller('navigationController', ['$scope', '$location', navigationCtrl]);
  };
  return {init: init};
});

All it does is return an object with an init() function. This function get the controller constructor functions (route1Ctrl, route2Ctrl, navigationCtrl), and register them with AngularJS like normal. The init() function is triggered in app.js, like so:

    controllers.init(app);

This is exactly identical to how the services, factories, and directives are registered with AngularJS. What does the bootstrap() method in app.js do? Let’s see.

var bootstrap = function(app) {
  var deferred = Q.defer();
  var injector = angular.bootstrap($('#ExampleApp'), ['ExampleApp']);
  deferred.resolve([injector, app]);

  return deferred.promise;

}

It simply takes the Angular app object (which has hopefully been initialised inside the init() method), and actually bootstraps it, binding it to a DOM element in the HTML. It then returns a Q promise, which contains an object with two fields.

  • The injector, which is responsible for Angular’s dependency injection. This is not so useful in the actual production code, but is handy in unit tests, when you need to get a handle to something registered with AngularJS.
  • The app, which is the app object itself. You could use it in your unit tests to perform further configuration changes.

The above two methods, used in conjunction inside bootstrap.js, starts up our app.

In the next post, I shall discuss how the test environment is set up.