gerasalus
gerasalus

Reputation: 7707

Angularjs - how to correctly replace service dependency with a mock

I'm using yeoman generator created app, and doing my tests in karma.

I have reusable mock objects for every of my service. How do i correctly replace specific service dependcy with a mock, so i could then use jasmine to spy upon methods

So far i have done like this:

My service:

angular.module('ql')
  .service('loginService', ['$http','API','authService', function ($http, API, authService) {
    return {
      //service implementation
    }]);

Mock of authService:

'use strict';
//lets mock http auth  service, so it would be spied upon.
ql.mock.$authServiceMockProvider = function() {
  this.$get = function() {
    var $service = {
      loginConfirmed: function() { }
    };
    return $service;
  };
};

//and register it.
angular.module('qlMock').provider({
  $authServiceMock: ql.mock.$authServiceMockProvider
});

And my test:

'use strict';

describe('When i call login method()', function () {

  // load the service's module
  beforeEach(module('ql'));
  beforeEach(angular.mock.module('qlMock'));

  // instantiate service
  var loginService,
  authService,
  $httpBackend;
  beforeEach(function() {
    // replace auth service with a mock.
    // this seems kind of dirty... is there a bettery way? 
    module(function($provide, $injector){
      authService = $injector.get('$authServiceMockProvider').$get();
      $provide.value('authService', authService);
    });

    //actually get the loginService
    /*jshint camelcase: false */
    inject(function(_loginService_, _$httpBackend_) {
      loginService = _loginService_;
      $httpBackend =_$httpBackend_;
    });

    //http auth module method, that should be call only on success scenarios
    spyOn(authService, 'loginConfirmed').andCallThrough();
  });
  it('it should do something', function () {
  //actual test logic
  });
});

What i do not like is the line:

authService = $injector.get('$authServiceMockProvider').$get();

I would like to simply somehow get the authServiceMock (without getting provider, and calling et method) and then inject it into loginService.

I know i could call my $authServiceMock simply authService, and provide it as a mock, so that it would always override my default implementation, but i do not want to do this.

Upvotes: 13

Views: 6670

Answers (3)

Nick
Nick

Reputation: 19684

I know this is late but maybe it will help someone who happen upon this post.

Mocking a service in Jasmine is quite simple using Angular's $provide service. The trick is to use $provide to swap out a service implementation before injecting the service.

For example let's say we are testing a service that makes use of the $location service to get information about the current URL.

// load the service's module under test
beforeEach(module('myExampleModule'));

// mock out $location with a fake one
beforeEach(
  module(function ($provide) {
    //create mock impl
    var mockLocation = {
      path: function () {
        return '/somewhere';
      }
    };

    // use $provide to swap the real $location with our mock
    $provide.value('$location', mockLocation);
  })
);

var $location;
// inject dependencies ($location will be our mocked $location)
beforeEach(inject(function (_$location_) {
  $location = _$location_;
}));

it('should return mock url', function () {
  var path = $location.path();

  //Assert that $location.path() returns '/somewhere'
  expect(path).toBe('/somewhere');
});

Upvotes: 15

Sten Muchow
Sten Muchow

Reputation: 6711

I have never unit tested a service in a service, not yet anyways but our authertication/login stuff is coming up soon.

As you are unit testing the loginService you are only interested in the way the service interacts with the data it is given by the AuthService and not that the AuthService is working correctly. Which is what you have set up in the mock.

I think this would be my approach: (inside the parent describe)

  var   
     loginService, 
     authService
     AUTH_DATA
  ;

  beforeEach(function() {
     module('ql');
     // I am assuming this is the global app module so both services live here? If not include this module as well
  });


   beforeEach(inject(function (_authService_, _loginService_) {
      authService = _authService_;
      loginService = _loginService_;
      //Now with the spy setup you intercept the calls to the service and you choose what data to return, based on the unit test. Now your LoginService can simply resond to the data it is give from the login service
    }));

  it('it should do something', function () {
     spyOn(authService, 'loginConfirmed').andReturn(AUTH_DATA);
     loginService.confirmLogin(); //Dont know your actual API but a contrived guess
     expect('something to happen in loginservice when AUTH_DATA is returned').toBe('Something else')
   });

Upvotes: 0

Pierre Gayvallet
Pierre Gayvallet

Reputation: 2953

I think I would simply use an angular service decorator to mock or totally replace your service for tests. Here is an example

Upvotes: 0

Related Questions