Jasmine test with Angularjs

In this article I will introuce to all of you some tips how to test and what you need to test angularjs with jasmine test. Controller, service and provider are the most important components for testing in angularjs.

Now let's start :

Services

Services are the common components in Angularjs. They provide you to store and re-usable logic that you can use and share them with controller, directive and other services.

Let's see the service's code below:

angular.module('services', [])
  .service('service_1', ['$window', 'service_2', function($window, service_2){
    this.showDialog = function(message, title){
      if(title){
        service_2.showModalDialog({
          title: title,
          message: message
        });
      } else {
        $window.alert(message);
      }
    };
  }]);

As you can see code above our service just has only one method showDialog. In the method, it maybe call $window or modalSvc according to our title's value for check condition.

So you want to test the service_1 you must to mock up both of service above and then load the angular module that include the service_1:

var mockWindow, mockService_2, serviceObj;
beforeEach(function(){
  module(function($provide){
    $provide.service('$window', function(){
      this.alert= jasmine.createSpy('alert');
    });
    $provide.service('service_2', function(){
      this.showModalDialog = jasmine.createSpy('showModalDialog');
    });
  });
  module('services');
});

beforeEach(inject(function($window, service_1, service_2 ){
  mockWindow=$window;
  mockService_2=service_2;
  serviceObj=service_1;
}));

It's time for you to test the method showDialog. you can test it with the two case as code below:

it('should show alert when title is not passed into showDialog', function(){
  var message="message";
  serviceObj.showDialog(message);

  expect(mockWindow.alert).toHaveBeenCalledWith(message);
  expect(mockService_2.showModalDialog).not.toHaveBeenCalled();
});

it('should show modal when title is passed into showDialog', function(){
  var message="message";
  var title="title";
  serviceObj.showDialog(message, title);

  expect(mockService_2.showModalDialog).toHaveBeenCalledWith({
    message: message,
    title: title
  });
  expect(mockWindow.alert).not.toHaveBeenCalled();
});

Provider

Now let's start testing the provider with sample code below. It will show you the provider_1 is depends on provider_2 and a constand appConstants:

angular.module('providers', [])
  .provider('provider_1', function(appConstants, provider_2){

    this.configureOptions = function(options){
      if(options.allow){
        provider_2.register(appConstants.ALLOW);
      } else {
        provider_2.register(appConstants.DENY);
      }
    };

    this.$get = function(){};

The same as testing Service, you must to mock up the privider_1 and appConstants. Before testing the provider, you need to make sure that the module is loaded and ready. In tests, loading of the modules is defferred till an inject block is executed or the first test is executed.

Code below show you the mock up and loads the modules:

beforeEach(module("providers"));
beforeEach(function(){
  module(function(provider_1, appConstants, provider_2){
    provider_1Obj = provider_1;
    provider_2Obj = provider_2;
    appConstantsObj = appConstants;
  });
});
beforeEach(inject());

Now you can call methods defined in the providers and start test them:

it('should call register with allow', function(){
  provider_1Obj.configureOptions({allow: true});
  expect(provider_2Obj.register).toHaveBeenCalled();
  expect( provider_2Obj.register).toHaveBeenCalledWith(appConstantsObj.ALLOW);
});

Controller

It's quit different from service, controller are not injectable, rather they are instantiated automatically when a route loads or, an ng-controller directive is compiled.The controller always is combine into view, the action of method in controller maybe depends in the views. So some objects maybe added to the scope after the view already compiled.If you to test the controller, objects must to be manually created and added to the controller.

angular.module('controllers',[])
  .controller('sampleController', ['$scope','service_1', function($scope, service_1) {
    $scope.saveData = function () {
      service_1.save($scope.bookDetails).then(function (result) {
        $scope.bookDetails = {};
        $scope.bookForm.$setPristine();
      });
    };
  }]);

To test this controller, we need to create an instance of the controller by passing in a $scope object and a mocked object of the service (dataSvc).

 module(function($provide){
  $provide.factory('service_1', ['$q', function($q)
    function save(data){
      if(passPromise){
        return $q.when();
      } else {
        return $q.reject();
      }
    }
    return{
      save: save
    };
  }]);
});

We can then create a new scope for the controller using the $rootScope.$new method. After creating an instance of the controller, we have all the fields and methods on this new $scope.

 beforeEach(inject(function($rootScope, $controller, service_1){
  scope = $rootScope.$new();
  mockDataSvc = service_1;
  spyOn(mockDataSvc,'save').andCallThrough();
  sController = $controller('sampleController', {
    $scope: scope,
    service_1: mockDataSvc
  });
}));

To test the saveData method, you must to set the values for the bookDetails and bookForm. These objects will be bound into view elements, and they are created at runtime when the view is compiled. You need to manually initialize them with some values before calling the saveData method.

it('should call save method on service_1 on calling saveData', function(){
    scope.bookDetails = {
      bookId: 1,
      name: "Mastering Web application development using AngularJS",
      author:"Peter and Pawel"
    };
    scope.bookForm = {
      $setPristine: jasmine.createSpy('$setPristine')
    };
    passPromise = true;
    scope.saveData();
    scope.$digest();
    expect(mockDataSvc.save).toHaveBeenCalled();
    expect(scope.bookDetails).toEqual({});
    expect(scope.bookForm.$setPristine).toHaveBeenCalled();
})

All Rights Reserved