Testing AngularJS under Node

Examples of unit testing Angular code from Node.

This is continuation of Server side Angular under Node.

If we can load and run an Angular application using synthetic browser environment under Node, how can use it for testing? This blog post describes unit testing basic angular components: modules, values, constants and services. I will use Mocha to run the BDD unit tests.

Setup and teardown

Before each unit test we need a clean application environment. After each test we want to destroy the entire environment (window and document objects). We can easily achieve this using the following commands to benv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var benv = require('benv');
describe('angular app', function () {
beforeEach(function setupEnvironment(done) {
benv.setup(function () {
benv.expose({
angular: benv.require('./bower_components/angular/angular.js', 'angular')
});
done();
});
});
afterEach(function () {
// completely destroy window and document objects
benv.teardown(true);
});
});

Because browser environment is asynchronous, we use done argument to let Mocha know when the setup has finished.

Loading the application itself

For simplicity, before each unit test let us load an angular module to test right inside the unit test code.

1
2
3
4
5
6
7
8
beforeEach(function loadMyApp() {
angular.module('foo', [])
.value('bar', 'baz')
.constant('name', 'jim')
.service('add', function () {
return function add(a, b) { return a + b; };
});
});

We could also load the module using Node's require method if the code is in the external file (as it is in typical application).

value-app.js
1
2
3
4
5
6
7
8
9
10
11
12
console.log('loading value app');
angular.module('foo', [])
.value('bar', 'baz')
.constant('name', 'jim')
.service('add', function () {
return function add(a, b) { return a + b; };
});
// spec.js
beforeEach(function loadMyApp() {
delete require.cache[require.resolve('./value-app')]; // 1
require('./value-app');
});

Notice that in order to force Node to actually evaluate value-app.js every time, we need to clear the evaluated value from the loaded modules cache (// 1).

Testing by injecting values

We can test individual entities inside angular module foo by loading them into the unit test using injector service. For example, we can check if the application module has a dependency by name bar.

1
2
3
4
it('has bar', function () {
var injector = angular.injector(['foo']);
expect(injector.has('bar')).toBe(true);
});

We can also test the actual value

1
2
3
4
it('has correct value', function () {
var injector = angular.injector(['foo']);
expect(injector.get('bar')).toEqual('baz');
});

Testing by running a test module

Creating an injector and getting separate values one by one is tiresome. Luckily, there is a nice method to get all necessary dependencies in a single shot using Angular itself. This can be done in 3 steps:

  • inside a unit test we can create another module (let us call it testFoo) purely for testing. We will make testFoo depend on the application module foo.
  • we will register a test callback with actual tests to be run using Angular module.run method. This way the test method can declare by name any dependencies it needs, and the angular dependency injection will load them and pass them into the method automatically.
  • finally, we will start the angular application by bootstrapping the application.

Here is the same unit test as above that checks the value of bar.

1
2
3
4
5
6
7
it('has correct bar value', function () {
angular.module('testFoo', ['foo'])
.run(function (bar) {
expect(bar).toEqual('baz');
});
angular.bootstrap(document, ['testFoo']);
});

Notice that run is executed immediately on the application bootstrap, thus the unit test is synchronous.

The boilerplate code that surrounds the run function can be moved to a helper method. I created ng-node-bdd repository that exposes ngIt function that can be used in place of the ordinary it spec function. It allows to declare modules and dependencies and run unit test right away

1
2
3
4
5
6
7
function ngIt(name, deps, cb) {
it(name, function () {
var m = angular.module('test-module', deps);
m.run(cb);
angular.bootstrap(document, ['test-module']);
});
}

cb is the actual unit test callback function with dependencies as arguments

1
2
3
4
var ngBdd = require('ng-node-bdd');
ngBdd.ngIt('gets value', ['foo'], function (bar) {
expect(bar).toEqual('baz');
});

testing service

Testing synchronous custom Angular services is also simple

value-app.js
1
2
3
4
5
6
7
8
angular.module('foo', [])
.service('add', function () {
return function add(a, b) { return a + b; };
});
// spec.js
ngIt('adds numbers', ['foo'], function (add) {
expect(add(2, 3)).toEqual(5);
});

Pretty cool!

Testing asynchronous code

What if our Angular service has asynchronous logic? For example, instead of adding two numbers and returning the sum, it could add them after an interval.

1
2
3
4
5
6
7
8
9
10
angular.module('foo', [])
.service('add', function ($q, $timeout) {
return function add(a, b) {
var deferred = $q.defer();
$timeout(function () {
deferred.resolve(a + b);
}, 1000);
return deferred.promise;
};
});

A typical asynchronous unit test in Mocha either returns a promise, or uses optional done argument to let the testing framework engine know it has finished. Unfortunately, we cannot simply return the promise from the unit test, because it gets lost in the module.run method. Thus we need to use done callback.

Trying to just declare done argument in the unit test callback does not work, because Angular tries to inject done argument

1
2
3
4
5
6
7
8
ngBdd.ngIt('adds numbers', ['foo'], function (add, done) {
return add(2, 3).then(function (result) {
expect(result).toEqual(5);
done();
});
});
// output
Error: [$injector:unpr] Unknown provider: doneProvider <- done

Luckily, we can easily inject the done callback as a value into the test module!

1
2
3
4
5
6
7
8
9
10
function ngIt(name, deps, cb) {
it(name, function (done) {
// done is callback injected by the Mocha's engine
var m = angular.module('test-module', deps);
// make it available to unit tests via Angular DI
m.constant('done', done);
m.run(cb);
angular.bootstrap(document, ['test-module']);
});
}

This makes Mocha's done callback available via Angular dependency injection, and the unit test above passed. The downside is that every unit test has to call done(), but this could be fixed later.

Update 1 - removing need to always call done from unit tests

We can determine if the unit test callback function cb actually lists done as one of its arguments using angular injector annotate method. It returns in the simplest case list of argument names (by parsing function's toString text).

1
2
function foo(bar) {}
angular.injector([]).annotate(foo); // ['bar']

Thus we can transform ngIt to provide done only if the test needs it

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function ngIt ...
it(name, function (done) {
var testDependencies = angular.injector([]).annotate(cb);
var isAsyncTest = testDependencies.indexOf('done') !== -1;

var m = angular.module('test-module', deps);
if (isAsyncTest) {
m.constant('done', done);
}
m.run(cb);
angular.bootstrap(document, ['test-module']);
if (!isAsyncTest) {
// call Mocha's done ourselves
done();
}
});

Update 2 - what's next?

See how you can load and unit test private parts in an Angular application that uses CommonJS modules: Unit testing Angular from Node like a boss.