115

I have an AngularJS service written and I would like to unit test it.

angular.module('myServiceProvider', ['fooServiceProvider', 'barServiceProvider']).
    factory('myService', function ($http, fooService, barService) {

    this.something = function() {
        // Do something with the injected services
    };

    return this;
});

My app.js file has these registered:

angular
.module('myApp', ['fooServiceProvider','barServiceProvider','myServiceProvider']
)

I can test the DI is working as such:

describe("Using the DI framework", function() {
    beforeEach(module('fooServiceProvider'));
    beforeEach(module('barServiceProvider'));
    beforeEach(module('myServiceProvder'));

    var service;

    beforeEach(inject(function(fooService, barService, myService) {
        service=myService;
    }));

    it("can be instantiated", function() {
        expect(service).not.toBeNull();
    });
});

This proved that the service can be created by the DI framework, however next I want to unit test the service, which means mocking out the injected objects.

How do I go about doing this?

I've tried putting my mock objects in the module, e.g.

beforeEach(module(mockNavigationService));

and rewriting the service definition as:

function MyService(http, fooService, barService) {
    this.somthing = function() {
        // Do something with the injected services
    };
});

angular.module('myServiceProvider', ['fooServiceProvider', 'barServiceProvider']).
    factory('myService', function ($http, fooService, barService) { return new MyService($http, fooService, barService); })

But the latter seems to stop the service being created by the DI as all.

Does anybody know how I can mock the injected services for my unit tests?

Thanks

David

Amy Pellegrini
  • 3,200
  • 1
  • 13
  • 19
BanksySan
  • 27,362
  • 33
  • 117
  • 216
  • You can take a look at [this](http://stackoverflow.com/questions/14596687/initializing-angularjs-resource-with-in-page-json/14598672#14598672) answer of mine to another question, I hope it could be helpful to you. – remigio Feb 08 '13 at 14:01
  • Also look at http://stackoverflow.com/questions/14238490 – jab Jun 28 '13 at 20:04

7 Answers7

184

You can inject mocks into your service by using $provide.

If you have the following service with a dependency that has a method called getSomething:

angular.module('myModule', [])
  .factory('myService', function (myDependency) {
        return {
            useDependency: function () {
                return myDependency.getSomething();
            }
        };
  });

You can inject a mock version of myDependency as follows:

describe('Service: myService', function () {

  var mockDependency;

  beforeEach(module('myModule'));

  beforeEach(function () {

      mockDependency = {
          getSomething: function () {
              return 'mockReturnValue';
          }
      };

      module(function ($provide) {
          $provide.value('myDependency', mockDependency);
      });

  });

  it('should return value from mock dependency', inject(function (myService) {
      expect(myService.useDependency()).toBe('mockReturnValue');
  }));

});

Note that because of the call to $provide.value you don't actually need to explicitly inject myDependency anywhere. It happens under the hood during the injection of myService. When setting up mockDependency here, it could just as easily be a spy.

Thanks to loyalBrown for the link to that great video.

Community
  • 1
  • 1
John Galambos
  • 2,801
  • 2
  • 26
  • 24
  • this worked for me, but i could omit the `beforeEach(module('myModule'));`. i used two `beforeEach`, one for the provide, the other for injecting the service – danza May 15 '14 at 10:21
  • 13
    Works great, but beware a detail: the `beforeEach(module('myModule'));` call **HAS** to come before the `beforeEach(function () { MOCKING })` call, or else the mocks will get overwritten by the real services! – Nikos Paraskevopoulos Dec 16 '14 at 09:11
  • 1
    Is there a way to mock not service but constant in the same manner? – Artem Feb 27 '15 at 11:48
  • 1
    Sorry, it was easy. To mock constant dependency, we just need to use $.provide.constant() instead of $provide.value(). – Artem Feb 27 '15 at 11:51
  • 5
    Similar to Nikos' comment, any ```$provide``` calls must be made before using ```$injector``` otherwise, you'll receive an error: ```Injector already created, can not register a module!``` – providencemac Mar 25 '15 at 20:33
  • 7
    What if your mock needs $q? Then you can't inject $q into the mock prior to calling module() in order to register the mock. Any thoughts? – Jake May 07 '15 at 15:15
  • 4
    If you're using coffeescript and you're seeing `Error: [ng:areq] Argument 'fn' is not a function, got Object`, make sure to put a `return` on the line after `$provide.value(...)`. Implicitly returning `$provide.value(...)` caused that error for me. – yndolok Jul 17 '15 at 17:10
  • And what if one one to put `mockDependency` in another file, without declaring a global variable ? – Cyril CHAPON Sep 08 '15 at 12:29
  • 1
    @Jake: you can use $provide.factory instead of $provide.value to use an injectable factory function in which you can get $q: `$provide.factory("dep", function($q){ return {}; })` – Oliver Hanappi Mar 30 '16 at 09:42
  • Any call to `$provide` after using `$injector` or `inject()` would fail stating: `Injector already created, can not register a module!`. – Alok Mishra May 30 '16 at 11:19
  • This works too for services app.services(XXXX. The message of error from angular is not intuitive. – cabaji99 May 08 '17 at 21:39
4

The way I look at it, there's no need to mock the services themselves. Simply mock the functions on the service. That way, you can have angular inject your real services as it does throughout the app. Then, mock the functions on the service as needed using Jasmine's spyOn function.

Now, if the service itself is a function, and not an object that you can use spyOn with, there's another way to go about it. I needed to do this, and found something that works pretty well for me. See How do you mock Angular service that is a function?

Community
  • 1
  • 1
dnc253
  • 39,967
  • 41
  • 141
  • 157
  • 3
    I don't think this answers the question. What if the factory of the service being mocked does something non-trivial, like hit the server for data? That would be a good reason to want to mock it. You want to avoid the server call, and instead create a mock version of the service with fake data. Mocking $http isn't a good solution either, because then you're actually testing two services in one test, instead of unit-testing the two services in isolation. So I would like to re-iterate the question. How do you pass a mock service to another service in a unit test? – Patrick Arnesen Jun 10 '13 at 22:14
  • 1
    If you're worried about the service hitting the server for data, that's what $httpBackend is for (http://docs.angularjs.org/api/ngMock.$httpBackend). I'm not sure what else would be a concern in the factory of the service that would require mocking the whole service. – dnc253 Jun 13 '13 at 16:33
2

Another option to help make mocking dependencies easier in Angular and Jasmine is to use QuickMock. It can be found on GitHub and allows you to create simple mocks in a reusable way. You can clone it from GitHub via the link below. The README is pretty self explanatory, but hopefully it might help others in the future.

https://github.com/tennisgent/QuickMock

describe('NotificationService', function () {
    var notificationService;

    beforeEach(function(){
        notificationService = QuickMock({
            providerName: 'NotificationService', // the provider we wish to test
            moduleName: 'QuickMockDemo',         // the module that contains our provider
            mockModules: ['QuickMockDemoMocks']  // module(s) that contains mocks for our provider's dependencies
        });
    });
    ....

It automatically manages all of the boilerplate mentioned above, so you don't have to write out all of that mock injection code in every test. Hope that helps.

tennisgent
  • 14,165
  • 9
  • 50
  • 48
2

In addition to John Galambos' answer: if you just want to mock out specific methods of a service, you can do it like this:

describe('Service: myService', function () {

  var mockDependency;

  beforeEach(module('myModule'));

  beforeEach(module(function ($provide, myDependencyProvider) {
      // Get an instance of the real service, then modify specific functions
      mockDependency = myDependencyProvider.$get();
      mockDependency.getSomething = function() { return 'mockReturnValue'; };
      $provide.value('myDependency', mockDependency);
  });

  it('should return value from mock dependency', inject(function (myService) {
      expect(myService.useDependency()).toBe('mockReturnValue');
  }));

});
Community
  • 1
  • 1
Ignitor
  • 2,907
  • 33
  • 50
1

If your controller is written to take in a dependency like this:

app.controller("SomeController", ["$scope", "someDependency", function ($scope, someDependency) {
    someDependency.someFunction();
}]);

then you can make a fake someDependency in a Jasmine test like this:

describe("Some Controller", function () {

    beforeEach(module("app"));


    it("should call someMethod on someDependency", inject(function ($rootScope, $controller) {
        // make a fake SomeDependency object
        var someDependency = {
            someFunction: function () { }
        };

        spyOn(someDependency, "someFunction");

        // this instantiates SomeController, using the passed in object to resolve dependencies
        controller("SomeController", { $scope: scope, someDependency: someDependency });

        expect(someDependency.someFunction).toHaveBeenCalled();
    }));
});
CodingWithSpike
  • 42,906
  • 18
  • 101
  • 138
  • 9
    The question is about services, which don't get instantiated in the test suite with a call to any equivalent service as $controller. In other words, one does not call $service() in a beforeEach block, passing in dependencies. – Morris Singer Feb 26 '15 at 19:18
1

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

To test 'myService' (from the "myApp" module) with its fooService and barService dependencies mocked out you simple can do the following in in your Jasmine test:

beforeEach(ModuleBuilder
    .forModule('myApp')
    .serviceWithMocksFor('myService', 'fooService', 'barService')
    .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/

Emil van Galen
  • 304
  • 1
  • 2
0

I know this is old question but there is another easier way ,you can create mock and disable the original injected at one function , it can be done by using spyOn on all the methods. see code below.

var mockInjectedProvider;

    beforeEach(function () {
        module('myModule');
    });

    beforeEach(inject(function (_injected_) { 
      mockInjectedProvider  = mock(_injected_);
    });

    beforeEach(inject(function (_base_) {
        baseProvider = _base_;
    }));

    it("injectedProvider should be mocked", function () {
    mockInjectedProvider.myFunc.andReturn('testvalue');    
    var resultFromMockedProvider = baseProvider.executeMyFuncFromInjected();
        expect(resultFromMockedProvider).toEqual('testvalue');
    }); 

    //mock all service methods
    function mock(angularServiceToMock) {

     for (var i = 0; i < Object.getOwnPropertyNames(angularServiceToMock).length; i++) {
      spyOn(angularServiceToMock,Object.getOwnPropertyNames(angularServiceToMock)[i]);
     }
                return angularServiceToMock;
    }
Gal Morad
  • 806
  • 7
  • 7