Quick Note for Unit Tests in Angularjs

Testing with web app is always fun. Angularjs makes it even better.
This quick note bootstrap any angular.js projects embracing with unit tests.

Tools

Unit testing in Angularjs is using (by default) Jasmine and Karma.

Also, angular-mocks needs to be installed. It is needed for injection and some other mock objects.

1
bower install --save angular-mocks

Npm dependencies

Following npm packages are needed and add them as devDependencies:

1
2
3
4
5
6
7
"jasmine-core": "^2.3.4",
"karma": "~0.12",
"karma-chrome-launcher": "^0.1.12",
"karma-firefox-launcher": "^0.1.6",
"karma-jasmine": "^0.3.5",
"karma-junit-reporter": "^0.2.2",
"protractor": "^2.1.0"

karma.conf.js

Configuration for karma:

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
29
30
31
32
33
34
35
module.exports = function(config){
config.set({
basePath : './',
files : [
'www/bower_components/angular/angular.js',
'www/bower_components/angular-ui-router/release/angular-ui-router.js',
'www/bower_components/angular-mocks/angular-mocks.js',
'www/bower_components/jquery/dist/jquery.js',
//all other lib dependencies
'www/app.gen.js', // app js file <- generated by browserfi
'www/app/**/test_*.js' // all tests file
],
autoWatch : true,
frameworks: ['jasmine'],
browsers : ['Chrome'],
plugins : [
'karma-chrome-launcher',
'karma-firefox-launcher',
'karma-jasmine',
'karma-junit-reporter'
],
junitReporter : {
outputFile: 'test_out/unit.xml',
suite: 'unit'
}
});
};

Put this file at root of project.

Run test

  1. install karma-cli: npm i -g karma-cli
  2. run karma start karma.conf.js

This will watch all files and re-run tests if any file changes. Strongly recommended having this opened when developing.

Unit tests for angular.js

Once above setup are done, it is ready to write unit tests.

Simple example

Add test_user.js to www/app/user/ folder or similar. Just keep test_ file name prefix.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
describe("user module",function(){
beforeEach(module('user'));
describe("auth factory",function(){
it ("should manage user session",inject(function(auth){
auth.initUserWithToken("abc");
expect(auth.getToken().token).toEqual("abc");
auth.setPin("1234");
expect(auth.getToken().pin).toEqual("1234");
expect(auth.validatePin("1234")).toBe(true);
expect(auth.validatePin("2234")).toBe(false);
auth.logout();
expect(auth.validatePin("1234")).toBe(false);
expect(auth.getToken()).toBe(null);
}));
})
});

Above, it first injects user module and then runs simple tests.

Override providers (factory / service)

If component depends on other providers, it’s able to use jasmine.createSpy to create dummy function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
describe("account module",function(){
beforeEach(module('account',function($provide){
$provide.value('server', {
call: jasmine.createSpy('call'),
setHeader:jasmine.createSpy('setHeader')
});
}));
describe("account factory",function(){
it ("should manage accounts",inject(function(account,server){
account.save();
expect(server.call).toHaveBeenCalled();
}));
})
});

Use $httpBackend for $http calls

Example:

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
describe("downStreamStore",function(){
var ds,s,ht;
beforeEach(module("dataSync"))
beforeEach(inject(function(downStreamStore,server,$httpBackend){
ds=downStreamStore;
s=server;
ht=$httpBackend;
}))
afterEach(function() {
ht.verifyNoOutstandingExpectation();
ht.verifyNoOutstandingRequest();
});
it ("should retrieve data",function(){
ht.when("GET","/test").respond({"hello":"test"})
ht.expectGET("/test");
ds.syncData("test","/test")
.then(function(){
expect(ds.get("test").hello).toBe("test");
})
.catch(function(e){
expect(e).toBeUndefined();
})
ht.flush();
});
})

the test above is synchrounous but with promises. Therefore no need use Jasmine async with done;

Quick Reference: