Mock a service in order to test a controller

2019-01-30 08:08发布

I have a ParseService, that I would like to mock in order test all the controllers that are using it, I have been reading about jasmine spies but it is still unclear for me. Could anybody give me an example of how to mock a custom service and use it in the Controller test?

Right now I have a Controller that uses a Service to insert a book:

BookCrossingApp.controller('AddBookCtrl', function ($scope, DataService, $location) {

    $scope.registerNewBook = function (book) {
        DataService.registerBook(book, function (isResult, result) {

            $scope.$apply(function () {
                $scope.registerResult = isResult ? "Success" : result;
            });
            if (isResult) {
                //$scope.registerResult = "Success";
                $location.path('/main');
            }
            else {
                $scope.registerResult = "Fail!";
                //$location.path('/');
            }

        });
    };
});

The service is like this:

angular.module('DataServices', [])

    /**
     * Parse Service
     * Use Parse.com as a back-end for the application.
     */
    .factory('ParseService', function () {
        var ParseService = {
            name: "Parse",

            registerBook: function registerBook(bookk, callback) {

                var book = new Book();

                book.set("title", bookk.title);
                book.set("description", bookk.Description);
                book.set("registrationId", bookk.RegistrationId);
                var newAcl = new Parse.ACL(Parse.User.current());
                newAcl.setPublicReadAccess(true);
                book.setACL(newAcl);

                book.save(null, {
                    success: function (book) {
                        // The object was saved successfully.
                        callback(true, null);
                    },
                    error: function (book, error) {
                        // The save failed.
                        // error is a Parse.Error with an error code and description.
                        callback(false, error);
                    }
                });
            }
        };

        return ParseService;
    });

And my test so far look like this:

describe('Controller: AddBookCtrl', function() {

    //  // load the controller's module
    beforeEach(module('BookCrossingApp'));


    var AddBookCtrl, scope, book;

    // Initialize the controller and a mock scope
    beforeEach(inject(function($controller, $rootScope) {
        scope = $rootScope;
        book = {title: "fooTitle13"};
        AddBookCtrl = $controller('AddBookCtrl', {
            $scope: scope
        });
    }));

    it('should call Parse Service method', function () {

        //We need to get the injector from angular
        var $injector = angular.injector([ 'DataServices' ]);
        //We get the service from the injector that we have called
        var mockService = $injector.get( 'ParseService' );
        mockService.registerBook = jasmine.createSpy("registerBook");
        scope.registerNewBook(book);
        //With this call we SPY the method registerBook of our mockservice
        //we have to make sure that the register book have been called after the call of our Controller
        expect(mockService.registerBook).toHaveBeenCalled();
    });
    it('Dummy test', function () {
        expect(true).toBe(true);
    });
});

Right now the test is failing:

   Expected spy registerBook to have been called.
   Error: Expected spy registerBook to have been called.

What I am doing wrong?

4条回答
姐就是有狂的资本
2楼-- · 2019-01-30 08:37

Following Javito's answer 4 years after-the-fact. Jasmine changed their syntax in 2.0 for calling through to real methods on spies.

Change:

spyOn(ParseServiceMock, 'registerBook').andCallThrough();

to:

spyOn(ParseServiceMock, 'registerBook').and.callThrough();

Source

查看更多
ら.Afraid
3楼-- · 2019-01-30 08:46

Include angular-mocks.js in your project and read carefully through the following link.

查看更多
地球回转人心会变
4楼-- · 2019-01-30 08:48

What I was doing wrong is not injecting the Mocked Service into the controller in the beforeEach:

describe('Controller: AddBookCtrl', function() {

    var scope;
    var ParseServiceMock;
    var AddBookCtrl;

    // load the controller's module
    beforeEach(module('BookCrossingApp'));

    // define the mock Parse service
    beforeEach(function() {
        ParseServiceMock = {
            registerBook: function(book) {},
            getBookRegistrationId: function() {}
       };
   });

   // inject the required services and instantiate the controller
   beforeEach(inject(function($rootScope, $controller) {
       scope = $rootScope.$new();
       AddBookCtrl = $controller('AddBookCtrl', {
           $scope: scope,
           DataService: ParseServiceMock
       });
   }));

   it('should call registerBook Parse Service method', function () {
       var book = {title: "fooTitle"}

       spyOn(ParseServiceMock, 'registerBook').andCallThrough();
       //spyOn(ParseServiceMock, 'getBookRegistrationId').andCallThrough();
       scope.registerNewBook(book);

       expect(ParseServiceMock.registerBook).toHaveBeenCalled();
       //expect(ParseServiceMock.getBookRegistrationId).toHaveBeenCalled();
    });
});
查看更多
爱情/是我丢掉的垃圾
5楼-- · 2019-01-30 08:50

You can inject your service and then use spyOn.and.returnValue() like this:

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

beforeEach(angular.mock.inject(function($rootScope, $controller, ParseService) {
    mock = {
        $scope: $rootScope.$new(),
        ParseService: ParseService
    };
    $controller('AddBookCtrl', mock);
}));

it('should call Parse Service method', function () {
    spyOn(mock.ParseService, "registerBook").and.returnValue({id: 3});

    mock.$scope.registerNewBook();

    expect(mock.ParseService.registerBook).toHaveBeenCalled();
});
查看更多
登录 后发表回答