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 | var benv = require('benv'); |
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 | beforeEach(function loadMyApp() { |
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).
1 | console.log('loading 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 | it('has bar', function () { |
We can also test the actual value
1 | it('has correct value', function () { |
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 maketestFoo
depend on the application modulefoo
. - 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 | it('has correct bar value', function () { |
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 | function ngIt(name, deps, cb) { |
cb
is the actual unit test callback function with dependencies as arguments
1 | var ngBdd = require('ng-node-bdd'); |
testing service
Testing synchronous custom Angular services is also simple
1 | angular.module('foo', []) |
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 | angular.module('foo', []) |
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 | ngBdd.ngIt('adds numbers', ['foo'], function (add, done) { |
Luckily, we can easily inject the done
callback as a value into the test module!
1 | function ngIt(name, deps, cb) { |
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 | function foo(bar) {} |
Thus we can transform ngIt
to provide done
only if the test needs it
1 | function ngIt ... |
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.