Unit testing Angular load using Node

Angular library can be loaded under Node synthetic browser environment.

I recently wrote stop-angular-overrides function to proxy calls to angular.module and prevent silent module overrides. As a good person, I wanted to unit test my script, setup continuous integration on Travis, etc. I would not use someone else's untested script, would you?

How can I unit test the script that is supposed to load between angular.js and user's code? At least I needed two different environments:

  1. Load angularjs and user scripts and make sure the last module with same name overrides first.
  2. Load angularjs, stop-angular-overrides.js and user scripts and catch the exception in the user scripts.

The problem with this testing is that we need fresh browser environment, and total script loading control. This is very different from normal unit testing, where all 3rd party and user code is loaded first, then all unit tests run.

Luckily, just two weeks ago I looked into unit testing D3 code using Nodejs without using any browsers (even without PhantomJs). Using a browser environment simulation under node allows complete control over the test setup and execution.

Here is the Nodejs test loading new synthetic browser using benv.

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
var benv = require('benv');
var Q = require('q');
QUnit.module('angular overrides', {
setup: function () {
var defer = Q.defer();
benv.setup(function () {
// window, document objects are setup
defer.resolve();
});
return defer.promise;
},
teardown: function () {
benv.teardown();
}
});
QUnit.test('loading angular', function () {
var angular = benv.require('../bower_components/angular/angular.js', 'angular');
console.log(angular);
// prints
{
element: { [Function: JQLite] cache: {}, expando: 'ng-1397439791767', _data: [Function] },
bootstrap: [Function: bootstrap],
copy: [Function: copy],
extend: [Function: extend],
equals: [Function: equals],
....
}
});

Notice how we extract window.angular from the loaded module using benv.require method.

There was just tiny snag loading angular in node: while 99% of the source code resides inside the immediately executed function and correctly works with the local angular variable, the very last line breaks everything!

1
2
3
4
5
6
7
8
9
10
11
/**
* @license AngularJS v1.2.16
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, document, undefined) {'use strict';
...
var angular = window.angular || (window.angular = {}),
...
})(window, document);
!angular.$$csp() && angular.element(document).find('head').prepend(...);

The fix that allows loading angularjs is literally single word prefix twice:

1
!window.angular.$$csp() && window.angular.element(document).find('head').prepend(...);

Testing silent override

First, let's test the default angular behavior - silent override.

1
2
3
4
5
6
7
QUnit.test('last module overrides by default', function () {
var angular = benv.require('../bower_components/angular/angular.js', 'angular');
var first = angular.module('A', []);
QUnit.equal(angular.module('A'), first, 'A -> first module');
var second = angular.module('A', []);
QUnit.equal(angular.module('A'), second, 'A -> second module');
});

Testing exception on override

To prove that stop-angular-overrides.js works as claimed, we need to load it using benv.require after loading angular.

1
2
3
4
5
6
7
8
9
QUnit.test('stop angular override', function () {
var angular = benv.require('../bower_components/angular/angular.js', 'angular');
benv.require('../stop-angular-overrides.js');
var first = angular.module('A', []);
QUnit.equal(angular.module('A'), first, 'A -> first module');
QUnit.throws(function () {
var second = angular.module('A', []);
}, 'Error');
});

I wrote unit tests to check filter and controller overrides, bringing total source code coverage to about 90%.