Unit Testing in AngularJS with Mocha, Chai, Sinon & ngMock
When I started learning about unit testing in AngularJS one of the first things I noticed is the number of different libraries needed to get the job done (Mocha, Chai, Sinon, ngMock, Karma, Istanbul...) and I found it difficult getting my head around exactly what does what, so in case there's anybody else going through the same thing I thought I'd share my experience.
I'm assuming you're already familiar with the concept of unit testing and it's benefits so I'll just jump straight into the nitty gritty.
AngularJS Unit Testing Tools/Libraries
I decided to go with the following set of tools for writing my unit tests, but most of these have various alternatives and can be used in multiple combinations:
- Mocha – testing framework for AngularJS unit tests (http://mochajs.org/)
- Chai – assertion library for AngularJS unit tests (http://chaijs.com/)
- Sinon – spy, stub and mock library for AngularJS unit tests (http://sinonjs.org/)
- ngMock – module for injecting and mocking angular services in AngularJS unit tests (https://docs.angularjs.org/api/ngMock)
Because the usage of these libraries is so intertwined I thought the best way to show how they work is with a complete example that highlights which parts are handled by each library.
See the Angular unit tests in action on Plunker
A plunk showing the below AngularJS unit tests running is available at http://plnkr.co/edit/9aBcNM?p=preview.
A simple AngularJS service / factory to be unit tested
To start with here's a very simple AngularJS service with just one method that logs a message, admittedly not the most useful service in the world but it'll do for our purposes.
(function () {
'use strict';
angular
.module('app')
.factory('SimpleService', Service);
function Service($log) {
var service = {
DoSomething: doSomething
};
return service;
function doSomething() {
$log.info('something done!');
}
}
})();
An AngularJS unit test for the simple service
We've got a single unit test defined for the simple service that tests the behaviour of the DoSomething()
method.
Before each unit test we get a reference to the simple service from the AngularJS inject function provided by the ngMock module. The inject function allows dependencies to be wrapped in underscores so we can assign the real service names to the variables used in our unit tests.
The test uses the Arrange Act Assert pattern to keep the unit test code clean and organised, there's plenty of information online about the pattern if you're interested in finding out more.
describe('SimpleService', function () {
// define variables for the services we want to access in tests
var SimpleService,
$log;
beforeEach(function () {
// load the module we want to test
module('app');
// inject the services we want to test
inject(function (_SimpleService_, _$log_) {
SimpleService = _SimpleService_;
$log = _$log_;
})
});
describe('#DoSomething', function () {
it('should log the message "something done!"', function () {
// Arrange
sinon.spy($log, 'info');
// Act
SimpleService.DoSomething();
// Assert
assert($log.info.calledOnce);
assert($log.info.calledWith('something done!'));
// Cleanup
$log.info.restore();
});
});
});
A visual breakdown of the AngularJS unit test for the simple service
The below image shows which parts of the unit test are handled by each of the libraries listed above - Mocha, Chai, Sinon and ngMock.
Mocha is a javascript testing framework used to define our overall unit test with describe
, beforeEach
and it
functions. There are other functions available in Mocha including before
, after
and afterEach
.
Chai is an assertion library used to verify the results of our unit tests, this example uses the assert
interface from Chai, but you could also use the should
and/or expect
interfaces, they all do essentially the same thing it just comes down to personal preference.
Sinon is used for creating test spies, stubs and mocks in javascript, in our test it's used to create a spy on the $log.info()
method so we can verify that the method was called once and with the expected parameter. After the test the restore()
method is called to remove the spy and restore the method back to it's original state.
ngMock is used for injecting and mocking AngularJS services in unit tests, in our test it's used to load the AngularJS module we want to test by calling module('app')
, and also to inject references to the services we want to test using the inject(...)
function.
Unit Testing AngularJS Controllers
UPDATE June 2017: I expanded the example to include the below simple AngularJS controller and unit test in response to a few comments asking how to unit test AngularJS controllers.
A simple AngularJS controller to be unit tested
A very simple AngularJS controller that calls a method on the simple service to do something.
(function () {
'use strict';
angular
.module('app')
.controller('SimpleController', Controller);
function Controller(SimpleService) {
var vm = this;
initController();
function initController() {
// do something with the simple service
SimpleService.DoSomething();
}
}
})();
Unit test for the simple AngularJS controller
A unit test that verifies that the AngularJS controller calls the DoSomething()
method of the simple service once when the controller is instantiated.
An instance of the controller is created with the AngularJS $controller service before each test.
describe('SimpleController', function () {
// define variables for the services we want to access in tests
var SimpleController,
SimpleService;
beforeEach(function () {
// load the module we want to test
module('app');
// get services from injector
inject(function ($controller, _SimpleService_) {
SimpleService = _SimpleService_;
// spy on service method to check if it gets called later
sinon.spy(SimpleService, 'DoSomething');
// get controller instance from $controller service
SimpleController = $controller('SimpleController');
});
});
afterEach(function(){
// remove spy from service
SimpleService.DoSomething.restore();
});
describe('constructor', function () {
it('should do something with the SimpleService', function () {
// Assert
assert(SimpleService.DoSomething.calledOnce);
});
});
});
Additional AngularJS Unit Testing Tools
There are a few other unit testing tools worth mentioning for completeness, they're not used for writing the actual tests like the above libraries but help with other parts of the process:
- Karma – AngularJS unit test runner (https://karma-runner.github.io/)
- Istanbul – JavaScript unit test code coverage report generator that can be used with AngularJS (https://github.com/gotwarlost/istanbul)
- RequireJS – JavaScript file and module loader that can be used with AngularJS (http://requirejs.org/)
Karma - AngularJS Unit Test Runner
Karma is the de-facto test runner used to run unit tests for AngularJS, it’s a command line application that runs on NodeJS and is installed using NPM (node package manager).
Tests can technically be run directly in a browser without the need for a test runner, however some of the benefits that Karma brings are:
- It can monitor files for any changes and automatically run all tests when anything is updated, so you don’t need to remember to run your tests manually
- It can run tests in multiple browsers at the same time
- It’s run from the command line so it can be easily integrated as part of an automated build process
All of the configuration settings for tests are located in the “karma.conf.js” file, and include things like:
- which testing framework to load (e.g. Mocha)
- a list of other plugins to load (e.g. Chai, Sinon, Istanbul etc)
- the list of test files and application files to include and exclude
- which browser/s to use for running the tests (e.g. Chrome, PhantomJS)
- various other bits and pieces of information, for full details check out the karma docs
To start karma:
- Open a command window and navigate to the folder in your project that contains your karma.conf.js file
- Run the command: karma start
Istanbul - AngularJS Unit Test Code Coverage Report Generator
Istanbul is a code coverage tool for JavaScript, it can be used to generate HTML reports showing the percentage of coverage you have throughout your application and lets you drill down to exactly which lines are not covered.
If you have sections of your code that aren't testable or that you want to exclude from the coverage report you can use the comment /* istanbul ignore next */ above the block of code you want ignored like this:
/* istanbul ignore next */
function functionToIgnore() {
...
}
RequireJS + AngularJS
RequireJS is used to asynchronously load javascript files and dependencies, and to wrap code in modules that are used throughout the application.
While this library isn't used specifically for unit testing, if your project uses RequireJS then your unit tests will need to be setup differently, to see a plunk of the above example implemented with RequireJS go to http://plnkr.co/edit/JTL79b?p=preview
Need Some AngularJS Help?
Search fiverr for freelance AngularJS developers.
Follow me for updates
When I'm not coding...
Me and Tina are on a motorcycle adventure around Australia.
Come along for the ride!