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

57,713

Solution 1

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

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

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

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

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.

Share:
57,713
KevSheedy
Author by

KevSheedy

Updated on July 08, 2022

Comments

  • KevSheedy
    KevSheedy about 2 years

    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);
    
  • endorama
    endorama over 10 years
    To avoid cooffeescript implicit return you could simply add a return statement at the end of the function, so instead of returning null the function would simply not return anything. See stackoverflow.com/questions/15469580/…
  • Jim Aho
    Jim Aho over 9 years
    So this syntax is the proper one to basically mock schedule (or any service, controller, factory) in the myApplicationModule?
  • Jim Aho
    Jim Aho over 9 years
    Do you actually need the extra surrounding function? I.e. would it be possible to pass in the module function directly to beforeEach?
  • Nick Perkins
    Nick Perkins almost 9 years
    If you say "return" it will return "undefined" -- but both ways work fine, and this way is 2 chars shorter ;) --either way, you should probably add a comment on the line because the guy who deletes it by accident is in for the same pain that you had in the first place
  • Shiboe
    Shiboe almost 8 years
    This fixed my (frustrating) issue. Basically, as soon as I added $provide for mocking, my angular.mock.inject no longer ran/worked. Returning null allowed them to play nicely together.
  • Jelle den Burger
    Jelle den Burger almost 7 years
    The function wrapped around the module call is actually not needed.