Unit Testing in AngularJS applications with Karma and Jasmine

angular-js

With modern web applications becoming more complex and powerful, there is a strong need to leave the old “this should work” mentality behind and write code that is reliable. Customer expectations are higher than ever and no one accepts errors anymore. Unit tests can make you feel confident about your code by verifying that all components work as expected. But besides the increased confidence you will get, writing unit tests can also improve the quality of the code itself. To be able to test small parts of an application independently you are automatically forced to think about the structure and modularity of your code. And as a bonus, well written unit tests can serve as an easy to read documentation of a component’s functional behaviour.

What should be tested?

As the name implies unit tests should test small, isolated “units” of your application. To be able to test these parts independently the code has to be well structured and without dependencies all over the application. Luckily Angular was build with testability in mind and enforces the developer to a certain structure by building independent components like services, controllers, directives and filters. Each of these components can be tested separately without knowing the other parts of an application. A good approach when writing unit tests is to treat the tested components as black boxes which offer their own API and are only responsible for one specific purpose.

The Test Environment

Running automatic Javascript tests is not a big challenge thanks to a lot of great tools. At M-Way we use Karma as our test runner and Jasmine as the testing framework. When running the tests, Karma starts an instance of all browsers specified in the settings, loads the application code from its internal web server, runs the test files and reports the results of the browser instances back to the terminal via socket.io. Karma can be installed with npm and is configured by editing the “karma.conf.js” file. With Karma up and running all we need to do is to write some useful tests. As already said we use Jasmine as our favourite testing framework because it offers a clean and easy syntax and comes with helpful add-ons like build-in spies, stubs and asynchronous support. A good introduction to what Jasmine offers can be found on the Jasmine github page. Jasmine encourages a behaviour-driven syntax and supports grouping and nesting of test suites, a feature that should definitely be used to keep a clean structure throughout the test specs. A simple example of a test suite for angular filters in Jasmine looks like this:


describe('Filter', function(){
  
  describe('humanize', function(){
    it('should print yes if input is truthy', function(){…});
    it('should print no if input is falsy', function(){…});
  });
  
  describe('dashIfEmpty', function(){
    it('should print a dash if input is undefined', function(){…});
    it('should print a dash if input is an empty string', function(){…});
  });

});

AngularJS and Testability

One of the most read arguments why Angular is a great framework is that it was build with testing in mind. But what does this mean? Basically there are two main points which make writing unit tests for Angular applications relatively easy compared to many other frameworks.

Dependency injection

With Angular’s dependency injection it is possible to inject a singleton instance of a service into any component. For unit testing this means that these injected dependencies can easily be mocked so that only the components own code can be tested without its dependencies. As an example imagine a translation service which loads language files from a remote server on startup and exposes a function to translate keys with the help of these files. If the component you want to test has a dependency on this translation service it would try to load the language files every time the test runs. Obviously this is not the desired behaviour for an independent unit test. But thanks to the dependency injection system we can inject a mocked version of the translation service which does not load any server data and just returns the untranslated keys. Because the correct translations are not important for the behaviour of the tested component this approach is a good way to disconnect the behaviour of the two components during testing.

ngMock

The Angular framework includes a special module which makes testing a lot more painless. The ngMock module includes useful mocks for many common core services and provides global helper functions. One of the most important parts of ngMock is the $httpBackend service which offers the possibility to expect requests and respond to requests made by the $http service. $httpBackend offers a flush() method to immediately process the response handlers of a request. Similar to the $httpBackend service the $timeout service of ngMock also offers a flush() method which allows to skip the asynchronous behaviour. The global helper functions module() and inject() provided by ngMock offer a handy way to load a module and inject dependencies in your tests without bootstrapping the whole application. To load a module and inject a service you can simply write:


var DashboardService,
    $httpBackend;

beforeEach(module('App.Dashboard'));
beforeEach(inject(function(_DashboardService_, _$httpBackend_){
    DashboardService = _DashboardService_;
    $httpBackend = _$httpBackend_;
}));

The underscores around the service name will be trimmed by the injector and enable to use the original names for your variables. All together an example test for an Angular service would look like this:


describe('DashboardService', function(){

  var DashboardService,
      $httpBackend;

  beforeEach(module('App.Dashboard'));
  beforeEach(inject(function(_DashboardService_, _$httpBackend_){
    DashboardService = _DashboardService_;
    $httpBackend = _$httpBackend_;
  }));


  it('should load the dashboard widget types', function(){
    var widgets;
    //set response data of the mock server
    $httpBackend.when('GET', '/dashboard/widgetTypes').respond({…});
    DashboardService.getWidgetTypes().then( function(loadedWidgets){
      widgets = loadedWidgets;
    });
    $httpBackend.flush();
    expect(widgets).toBeDefined();
    expect(widgets.length).toBeGreaterThan(0);
  });
});

For more examples of tests for Angular components check out the official Angular unit test documentation.

Integrate the tests in your workflow

An important question when writing unit tests is when and where to run the tests? In general unit tests are really fast, so basically it is possible to run the tests on your local machine after every file change. But with an increasing number of tests the execution time reaches a limit where this setting is not practical anymore. Currently all unit tests for the Relution portal take about 20 seconds. That’s why we decided to run the tests on a pre-push hook with grunt. This way every time a team member wants to push his/her changes to the remote repository all tests have to pass first. This assures that only tested code reaches our CI server. Before the CI server starts building the project it also runs all tests on a headless PhantomJS browser to test that no breaking changes made their way into the code (for example because of a missing git hook or a manually forced push).

To have an overview of how much of our code is actually tested we included the code coverage tool Istanbul. Istanbul parses the code and adds statements to check which parts did run. Therefore all code files have to be processed by the Istanbul preprocessor before execution. The result is a detailed overview of all application code files and the lines and branches that have been executed. At this point it is important to say that a good code coverage is not the ultimate goal. A code coverage of 100% is definitely good but does not mean that your code is perfect. The fact that every part has been executed during the tests does not assure that there are no combinations which still could fail. Running your tests on a CI server enables you also to display the history of the test results in a neat graph visible for every project member. Karma can be configured to export test results with the jUnitReporter as an xml so that the CI server understands the data and displays the number of successful, failed and ignored tests.

Are unit tests enough?

Well written unit tests improve the code quality and confidence and are essential to nowadays web application development. They ensure that every part of an application works correctly on its own and prevent painful errors in production environments. But they are not the one and only key to reliable software. As unit tests concentrate on isolated parts of an application, end-to-end tests prove that all the small pieces also work well together. This is done by testing an application on a higher level using browser automation tools like Selenium. End-to-end tests with Angular are best done with Protractor, an integration test tool from the Angular team itself which is also absolutely worth checking out.

4 thoughts on “Unit Testing in AngularJS applications with Karma and Jasmine

  1. Pingback: It’s time to look back over the blog highlights 2015 | Thinking Mobile

  2. Raghu H R

    Hi, Can I know how to write unit test case without mocking httpBackEnd? is it possible to mock repositories directly?

    Reply
    1. Markus Pfeiffer

      What do you mean by mocking your repositories? The repository contains your code.

      You mock the server you run your requests against, so your test doesn’t depend on the availability of the real server (and doesn’t accidentally affect the real server).

      Reply
  3. Pingback: BackboneJS meets AngularJS | Thinking Mobile

Leave a Reply

Your email address will not be published. Required fields are marked *