0

I'm trying to test a controller that uses a service. When I run the following test and breakpoint within the inject function, the actual service is injected instead of the mock service. Why is the service defined through the $provider.factory not being injected?

"use strict";

describe("contestantController", function () {
    var dataService, rootScope, scope, passPromise, contestantController;

    beforeEach(function(){
        module(function ($provide) {

            //mock service
            $provide.factory('contestantDataService', ['$q', function ($q) {
                function save(data){
                    if(passPromise){
                        return $q.when();
                    } else {
                        return $q.reject();
                    }
                }
                function getData() {
                    if (passPromise) {
                        return $q.when(smallDataSet());
                    } else {
                        return $q.reject();
                    }
                }
                return {
                    addContestant: save,
                    getContestants: getData,
                };
            }]);

        });

        module('contestantApp');
    });

    beforeEach(inject(function ($rootScope, $controller, contestantDataService) {
        rootScope = $rootScope;
        scope = $rootScope.$new();
        dataService = contestantDataService;

        spyOn(dataService, 'getContestants').and.callThrough();

        contestantController = $controller('contestantController', {
            $scope: scope,
            contestantDataService: dataService
        });
    }));

    it('should call getContestants method on contestantDataService on calling saveData', function () {

        passPromise = true;
        rootScope.$digest();

        expect(dataService.getContestants).toHaveBeenCalled();
        expect(scope.contestants).toEqual(smallDataSet());
    });

});
Moppo
  • 18,797
  • 5
  • 65
  • 64
Steve Wash
  • 986
  • 4
  • 23
  • 50

2 Answers2

3

Tyler's answer will work, but the reasoning is a little off.

The module() function simply registers modules or module initialization functions that the inject() function will later use to initialize the Angular injector. It in no way links the mock service with the module.

Your code doesn't work because order in which the services are registered matters. Your code first registers a mock contestantDataService, then registers the contestantApp module, which contains its own contestantDataService, overwriting the registered mock. If you just move the module('contestantApp') call to the top, it should work as expected.

So this means that the two blocks below are equivalent and will both work...

beforeEach(function(){
  module('contestantApp');
  module(function ($provide) {
    ...
  });
);

and

beforeEach(function(){
  module('contestantApp', function ($provide) {
    ...
  });
);
Yunchi
  • 5,529
  • 2
  • 17
  • 18
2

Thx for @Yunchi . You pointed out my mistake.

You should invoke the module function before you mock the service contestantDataService.

Just update your code into this way,

//make sure contesttantDataService exist in this module in your production environment.

var dataService, rootScope, scope, passPromise, contestantController;
beforeEach(function(){
    //Add module name here which means we define the mockService under this module
    module('contestantApp', function ($provide) {
        //mock service
        $provide.factory('contestantDataService', ['$q', function ($q) {
            function save(data){
                if(passPromise){
                    return $q.when();
                } else {
                    return $q.reject();
                }
            }
            function getData() {
                if (passPromise) {
                    return $q.when(smallDataSet());
                } else {
                    return $q.reject();
                }
            }
            return {
                addContestant: save,
                getContestants: getData,
            };
        }]);
    });
    beforeEach(inject(function ($rootScope, $controller, contestantDataService) {
         rootScope = $rootScope;
         scope = $rootScope.$new();
         dataService = contestantDataService;

         spyOn(dataService, 'getContestants').and.callThrough();
         //no need to manually inject to our controller now.
         contestantController = $controller('contestantController', {
             $scope: scope
     });
}));

Instead of using $provide.factory I think it's better to use $provide.value in our unit test. Because we only need to make sure the contestantDataService is an object and have the function.

Here are some similar questions you can check,

Injecting a mock into an AngularJS service.

Mock a service in order to test a controller.

Here is the jsfiddle I just created.

Have fun. : )

Community
  • 1
  • 1
Tyler.z.yang
  • 2,402
  • 1
  • 18
  • 31