Injecting a mock into an AngularJS service
Asked Answered
A

7

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

Assonance answered 8/2, 2013 at 13:6 Comment(2)
You can take a look at this answer of mine to another question, I hope it could be helpful to you.Semantics
Also look at stackoverflow.com/questions/14238490Kcal
E
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.

Everett answered 12/9, 2013 at 5:53 Comment(11)
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 serviceElectrophone
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!Nedra
Is there a way to mock not service but constant in the same manner?Substitutive
Sorry, it was easy. To mock constant dependency, we just need to use $.provide.constant() instead of $provide.value().Substitutive
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!Talich
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?Acotyledon
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.Burnisher
And what if one one to put mockDependency in another file, without declaring a global variable ?Habit
@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 {}; })Rack
Any call to $provide after using $injector or inject() would fail stating: Injector already created, can not register a module!.Laevorotation
This works too for services app.services(XXXX. The message of error from angular is not intuitive.Garrettgarrick
M
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?

Mcleroy answered 8/2, 2013 at 20:32 Comment(2)
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?Mishmash
If you're worried about the service hitting the server for data, that's what $httpBackend is for (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.Mcleroy
B
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.

Bianchi answered 11/1, 2015 at 4:41 Comment(0)
H
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');
  }));

});
Hitchhike answered 19/10, 2015 at 12:26 Comment(0)
S
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();
    }));
});
Soong answered 9/6, 2014 at 19:4 Comment(1)
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.Dispenser
C
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/

Codger answered 29/7, 2014 at 0:42 Comment(0)
M
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;
    }
Mcinerney answered 30/6, 2015 at 8:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.