Injecting a mock into an AngularJS service

JavascriptAngularjsMockingJasmineAngularjs Service

Javascript Problem Overview


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

Javascript Solutions


Solution 1 - Javascript

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.

Solution 2 - Javascript

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 https://stackoverflow.com/questions/14531904/how-do-you-mock-angular-service-that-is-a-function

Solution 3 - Javascript

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.

Solution 4 - Javascript

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');
  }));

});

Solution 5 - Javascript

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();
    }));
});

Solution 6 - Javascript

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/

Solution 7 - Javascript

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;
    }

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
QuestionBanksySanView Question on Stackoverflow
Solution 1 - JavascriptJohn GalambosView Answer on Stackoverflow
Solution 2 - Javascriptdnc253View Answer on Stackoverflow
Solution 3 - JavascripttennisgentView Answer on Stackoverflow
Solution 4 - JavascriptIgnitorView Answer on Stackoverflow
Solution 5 - JavascriptCodingWithSpikeView Answer on Stackoverflow
Solution 6 - JavascriptEmil van GalenView Answer on Stackoverflow
Solution 7 - JavascriptGal MoradView Answer on Stackoverflow