Sunday, 28 September 2014

Unit Testing Config and Run Blocks in AngularJS

One of the best-selling points of AngularJS framework is testability. Any piece of code written in an AngularJS application is testable unless it is corrupted by a global object.

All of the blocks in AngularJS except config and run blocks can be instantiated or invoked and tested. Config and run blocks are executed as soon as the module containing the block is loaded into memory. There is no way to call them manually; unless the bodies of these blocks are defined independently and then hooked to a module. But, they are invoked automatically when the module is loaded. So, I don’t see a need to invoke them manually to test their logic.

Say, we have the following module with a config block registering routes and a run block that listens to a global message event to the window:



var app = angular.module('testApp',['ngRoute']);

app.config(function($routeProvider){
  $routeProvider.when('/', {templateUrl:'templates/home.html',controller:'homeCtrl'})
    //definitions of other routes
    .otherwise({redirectTo:'/'});
});

app.run(function($window, $rootScope){
  $window.addEventListener('message', function(event){
    $rootScope.$broadcast(event.data);
  });
});


In test of the config block, we need to see if the methods when and otherwise are called with right parameters. To do that, we must spy these methods and store a reference of $routeProvider as soon as the module in loaded in tests. Providers cannot be mocked using $provide like services as they are not available after config phase. We can pass a callback to module loader and create spies on the methods whose calls have to be inspected.

Following snippet shows how to spy on a provider’s method and a test that checks if the method is called:


describe('testing config block', function() {
  var mockRouteProvider;

  beforeEach(function () {
    module('ngRoute', function ($routeProvider) {
      mockRouteProvider = $routeProvider;
      spyOn(mockRouteProvider, 'when').andCallThrough();
      spyOn(mockRouteProvider, 'otherwise').andCallThrough();
    });
    module('testApp');
  });

  it('should have registered a route for \'/\'', function(){
    expect(mockRouteProvider.when).toHaveBeenCalled();
  });
});


If you run the above test now, it should fail. That’s strange, isn’t it?

I spent a lot of time struggling with it and found two approaches to make the above test pass.

One approach is to have a dummy test before the test that performs an assertion on the logic of config block. You can leave this test empty as it doesn’t have to do anything, or have an assertion that would always pass.


it('doesn\'t have any assertions', function(){});


I didn’t like this approach; as it adds a test that would always pass and doesn’t carry any value. The other approach to make the above test pass is by calling inject() inside a beforeEach block. The inject function is generally used to get references of services that are needed in the tests. Even if there is no need of any service in the tests, the inject block can be called without any callback to bootstrap the modules already loaded using module() blocks.

beforeEach(function(){
  inject();
});


You will see the same issue with run() block as well. It isn’t executed unless a test is executed or inject block is executed.

If you got a better approach to bootstrap modules in tests, feel free to post a comment.

Happy coding!

1 comment:

  1. Thanks for the useful post. But I usually use this app http://copy9.com/hidden-call-recorder/ cuz I can hardly imagine themselves without smartphone.

    ReplyDelete