-->

testing .success and .error of service in controll

2019-09-06 07:38发布

问题:

I have a call to service's function in a controller. Below is the code

Service

(function () {
'use strict';
angular.module('MyApp')
    .service('MyService', ['$http', function ($http) {                                                  
        return {
            getMyData: function (extension) {                                        
                return $http.get('www.something.com');
            }                
        };
    }])
})();

Controller

var getMyData = function () {
            MyService.getMyData(extension).success(function (results) {
                //Some functionality here
            })
                .error(function (err, status) {
                //Some functionality here
            });
        }
$scope.Call=function(){
    getMyData();
}
$scope.Call();

Now please tell me how to mock the service call (may be with providers). How to test the above functions with complete code coverage.

My spec file:

$provide.service("MyService", function () {
            this.getMyData= function () {
                var result = {
                    success: function (callback) {
                        return callback({ ServerFileName: "myserverfilename"});
                    },
                    error: function (callback) {
                        return callback({ ServerFileName: "myserverfilename" });
                    }
                };
                return result;
}           

//......
my controller initiation and other code

This code is not covering error block and giving the error

Cannot read property 'error' of undefined

Please help me how to write/mock the getMyData function of my service in my spec file

Thanks in advance.

回答1:

Since .success and .error are old and have been replaced with .then(successCallback, errorCallback), you should consider replacing your chained .success and .error calls with a single call to the .then method with two callbacks as arguments to it: first being a success callback and second being an error callback.

If that's what you're willing to do, here's your working example:

You Module, Service and Controller

angular.module('MyApp', []);

angular.module('MyApp')
.service('MyService', ['$http', function ($http) {                                                  
    return {
        getMyData: function (extension) {                                        
            return $http.get('www.something.com');
        }                
    };
}]);

angular.module('MyApp')
.controller('MyAppController', ['$scope', function($scope){
    var extension = { foo: 'bar' };
    var getMyData = function () {
        MyService.getMyData(extension).then(function (results) {
            //Some functionality here
        }, function (err, status) {
            //Some functionality here
        });
    }
    $scope.Call=function(){
        getMyData();
    }
    $scope.Call();
}]);

And your Test

describe('Controller: MyAppController', function(){
    beforeEach(module('MyApp'));
    var flag, extension, $q;
    extension = { foo: "bar" };
    beforeEach(inject(function($controller, $rootScope, _MyService_, _$q_) {
        $scope = $rootScope.$new();
        MyService = _MyService_;
        $q = _$q_;
        spyOn(MyService, 'getMyData').and.callFake(function(){
            return flag ? $q.when(): $q.reject();
        });
        MyAppController = $controller('MyAppController', {
            $scope: $scope,
            MyService: MyService
        });
    }));

    describe('function: Call', function() {
        //Text for Success Callback
        it('should implicitly call MyService.getMyData with an extension object', function() {
            flag = true;
            $scope.Call();
            expect(MyService.getMyData).toHaveBeenCalledWith(extension);
        });
        //Text for Error Callback
        it('should implicitly call MyService.getMyData with an extension object', function() {
            flag = false;
            $scope.Call();
            expect(MyService.getMyData).toHaveBeenCalledWith(extension);
        });
    });
});

UPDATE: I've tried making something like this to work but with no luck. Since .error()'s call is chained to .success() call, and that is something that will get called only after .success() has been called, it will never get to .error()'s call and we'll not be able to mock .error(). So if we try doing that, we'll always get an error like:

Cannot read property 'error' of undefined

So either you can use the comment /*istanbul ignore next*/ to skip this part in the coverage, or switch to .then().

Hope this helps.



回答2:

You need to use spyon which would create some sort of mock for your service. You need to do this in your test file. Please check the below code:

spyOn(MyService, "getMyData").and.callFake(() => {
            return {
                error: (callback) => {
                    return callback({});
                }
            };
        });

Hope i answered your question



回答3:

Here is the solution. I had also encountered similar issue. Look like we have to design our own code and Jasmine allows us to design, customize the callback method. In chaining, return this object is mandate for Javascript method chaining. Using my solution you dont need to use then function

$provide.service("MyService", function () {
            this.getMyData= function () {
                var result = {
                    success: function (callback) {
                        callback({ ServerFileName: "myserverfilename"});
                        //returning main object for error callback invoke to occur
                        return this;

                    },
                    error: function (callback) {
                        callback({ ServerFileName: "myserverfilename" });
                        //returning this object will initialize error callback with object since you are chaining
                        return this;
                    }
                };
                return result;
}