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?
What is mock testing? Mocking means creating a fake version of an external or internal service that can stand in for the real one, helping your tests run more quickly and more reliably. When your implementation interacts with an object's properties, rather than its function or behavior, a mock can be used.
Mock Services help you handle situations where a piece of your application might not be available for testing when you need it. When combined with BlazeMeter performance testing functionality, Mock Services can make performance testing easier and more powerful.
Mock is a method/object that simulates the behavior of a real method/object in controlled ways. Mock objects are used in unit testing. Often a method under a test calls other external services or methods within it. These are called dependencies. Once mocked, the dependencies behave the way we defined them.
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(); }); });
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(); });
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With