April 09 2015

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:

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

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:

  1. Open a command window and navigate to the folder in your project that contains your karma.conf.js file
  2. 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

 

Web Development Sydney

Feel free to contact me if you're looking for web development or AngularJS development services in Sydney Australia, I also provide remote contracting services for clients outside Sydney.


Sponsored by