Search code examples
angularjsunit-testingjasmineangularjs-factoryangular-mock

How to unit test (using Jasmine) a function in a controller which calls a factory service which returns a promise


In the below SampleController, how do I unit test that postAttributes function calls sampleService.updateMethod. I'm having trouble since the updateMethod returns promise.

    angular.module('sampleModule')
       .controller('SampleController', SampleController);

    SampleController.$inject =['sampleService'];

    function SampleController(sampleService){

    this.postAttributes = function() {    
        sampleService.updateMethod(number,attributes)
            .then(function(response){
                //do something on successful update
            },function(response){
                //do something on unsuccessful update
            });
        }; 

    }

Here is the factory service that I have:

    angular.module('sampleModule')
        .factory('sampleService', sampleService);

    sampleService.$inject = ['$http'];

    function sampleService($http) {
        return {
            getMethod: function(acctNumber){
                return $http({
                    method: 'GET',
                    url: //api endpoint
                });
            },
            updateMethod: function(number, attributes){
                return $http({
                    method: 'PUT',
                    url: //api endpoint,
                    data: //payload
                });
            }
        };
    }

I would like to mock the factory service in the controller spec rather than injecting the actual service directly into $controller, since most of unit testing guidelines specify to test a unit under isolation.

Sample controller spec:

    describe('SampleController Test', function(){
        var $controller;
        var service;

        beforeEach(angular.mock.module('sampleModule')); 

        beforeEach(angular.mock.inject(function(_$controller_){
            $controller = _$controller_;
        }));

        it('Testing $scope variable', function(){
            var sampleController = $controller('SampleController', { 
                sampleService: service, //mocked factory service 
            });

            sampleController.postAttributes(); //calling the function first
            //here I would like to make an assertion to check if
            //sampleService.updateMethod has been called with certain parameters                        
            //how do I do that??
        });

    });

Solution

  • Googled around and found a solution to mock the factory service and followed promise creation approach like @TehBeardedOne and made it to return it from the mocked service.

        describe('SampleController', function(){
    
            var mockService, controller, deferred, $rootScope;
    
            beforeEach(function(){
    
                angular.mock.module('sampleModule');
    
                angular.mock.module(function($provide){
                    $provide.factory('sampleService', function($q){
                        function updateMethod(acct, attr){
                            deferred = $q.defer();
                            return deferred.promise;
                        }
                        return{updateMethod: updateMethod};
                    });
                });
    
                angular.mock.inject(function($controller, sampleService, _$rootScope_){
                    $rootScope = _$rootScope_;
                    mockService = sampleService;
                    spyOn(mockService, 'updateMethod').and.callThrough();
                    controller =$controller('SampleController', {
                        sampleService: mockService,
                    })
                });
            });
    
            it('postAttributes function should call updateMethod', function(){
    
                controller.postAttributes();
                expect(mockService.updateMethod).toHaveBeenCalled();
                expect(mockService.updateMethod).toHaveBeenCalledWith(controller.accountNumber, controller.attributes);
            });
    
            it('postAttributes success block', function(){
                controller.postAttributes();
                var res = {
                    data: '2323'
                }
                deferred.resolve(res);
                $rootScope.$digest();
                expect(//something in success block).toBe(something);
            });
    
            it('postAttributes failure block', function(){
                controller.postAttributes();
                var res = {
                    data: '9898'
                }
                deferred.reject(res);
                $rootScope.$digest();
                expect(controller.lame).toBe('dont type shit');
            });
    
        });
    

    I've mocked the sampleService with $provider service and made updateMethod to return a promise using $q. Later on you can resolve or reject the promise and test the success and failure blocks in individual it blocks.