How do you mock a service in AngularJS when unit testing with jasmine?

Unit TestingAngularjsJasmine

Unit Testing Problem Overview


Let's say I have a service shop that depends on two stateful services schedule and warehouse. How do I inject different versions of schedule and warehose into shop for unit testing?

Here's my service:

angular.module('myModule').service('shop', function(schedule, warehouse) {
    return {
        canSellSweets : function(numRequiredSweets){
             return schedule.isShopOpen()
                 && (warehouse.numAvailableSweets() > numRequiredSweets);
        }
    }
});

Here are my mocks:

var mockSchedule = {
    isShopOpen : function() {return true}
}
var mockWarehouse = {
    numAvailableSweets: function(){return 10};
}

Here are my tests:

expect(shop.canSellSweets(5)).toBe(true);
expect(shop.canSellSweets(20)).toBe(false);

Unit Testing Solutions


Solution 1 - Unit Testing

beforeEach(function () {
  module(function ($provide) {
    $provide.value('schedule', mockSchedule);
  });
});

Module is a function provided by the angular-mocks module. If you pass in a string argument a module with the corresponding name is loaded and all providers, controllers, services, etc are available for the spec. Generally they are loaded using the inject function. If you pass in a callback function it will be invoked using Angular's $injector service. This service then looks at the arguments passed to the callback function and tries to infer what dependencies should be passed into the callback.

Solution 2 - Unit Testing

Improving upon Atilla's answer and in direct answer to KevSheedy's comment, in the context of module('myApplicationModule') you would do the following:

beforeEach(module('myApplicationModule', function ($provide) {
  $provide.value('schedule', mockSchedule);
}));

Solution 3 - Unit Testing

With CoffeeScript I run in some issues so I use null at the end:

beforeEach ->
  module ($provide) ->
    $provide.value 'someService',
      mockyStuff:
        value : 'AWESOME'
    null

Solution 4 - Unit Testing

You can look here for more info

https://docs.angularjs.org/guide/services#unit-testing

You want to utilize the $provide service. In your case

$provide.value('schedule', mockSchedule);

Solution 5 - Unit Testing

As you are using jasmine, there is an alternative way to mock the calls with jasmine's spies (https://jasmine.github.io/2.0/introduction.html#section-Spies).

Using these you can be targeted with your function calls, and allow call throughs to the original object if required. It avoids clogging up the top of your test file with $provide and mock implementations.

In the beforeEach of your test I would have something like:

var mySchedule, myWarehouse;

beforeEach(inject(function(schedule, warehouse) {

  mySchedule = schedule;
  myWarehouse = warehouse;

  spyOn(mySchedule, 'isShopOpen').and.callFake(function() {
    return true;
  });

  spyOn(myWarehouse, 'numAvailableSweets').and.callFake(function() {
    return 10;
  });

}));

and this should work in similar fashion to the $provide mechanism, noting you have to provide local instances of the injected variables to spy on.

Solution 6 - Unit Testing

I recently released ngImprovedTesting module that should make mock testing in AngularJS way easier.

In your example you would only have to replace in your Jasmine test the ...

beforeEach(module('myModule'));

... with ...

beforeEach(ModuleBuilder.forModule('myModule').serviceWithMocks('shop').build());

For more information about ngImprovedTesting check out its introductory blog post: http://blog.jdriven.com/2014/07/ng-improved-testing-mock-testing-for-angularjs-made-easy/

Solution 7 - Unit Testing

It is simpler to put the mock on the module like this:

    beforeEach(function () {
    module('myApp');
    module({
      schedule: mockSchedule,
      warehouse: mockWarehouse
     }
    });
  });

you can use injection to get reference to these mocks for pre test manipulations :

var mockSchedule;
var mockWarehouse;
        
beforeEach(inject(function (_schedule_, _warehouse_) {
     mockSchedule = _schedule_;
     mockWarehouse = _warehouse_;
}));

Solution 8 - Unit Testing

I hope my answer is not that useless, but you can mock services by $provide.service

beforeEach(() => {
    angular.mock.module(
      'yourModule',
      ($provide) => {
        $provide.service('yourService', function() {
          return something;
        });
      }
    );
  });

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKevSheedyView Question on Stackoverflow
Solution 1 - Unit TestingAttila MiklosiView Answer on Stackoverflow
Solution 2 - Unit TestingMatt RichardsView Answer on Stackoverflow
Solution 3 - Unit TestingAlgimantas KrasauskasView Answer on Stackoverflow
Solution 4 - Unit TestingAdamView Answer on Stackoverflow
Solution 5 - Unit TestingbwobbonesView Answer on Stackoverflow
Solution 6 - Unit TestingEmil van GalenView Answer on Stackoverflow
Solution 7 - Unit TestingGal MoradView Answer on Stackoverflow
Solution 8 - Unit TestingClemens BaslerView Answer on Stackoverflow