Mock a service in order to test a controller
Solution 1
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();
});
});
Solution 2
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();
});
Solution 3
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();
![Javier Hertfelder](https://i.stack.imgur.com/NVUZV.jpg?s=256&g=1)
Javier Hertfelder
I am a Software engineer with more than 9 years of experience. In the last 4 years, I have assisted FXStreet - a leading Forex market information website - with the attainment of more than 4 million page views every month to recover from an unexpected “hiccup” 90% of the IT team left in less than 2 months. I was hired to accomplish two goals. Firstly, to rebuild the whole department from scratch. The second aim was to lead the reformation of the whole FXStreet Website and their 15 year old monolithic architecture. We switched from a monolithic to a microservices oriented architecture easy to maintain, monitor and develop. After one year and a half, thanks to an exceptional group of engineers, we achieved it, not without pain, not without effort. We now have a solid department where everybody has a voice, where everybody shares information and where everybody enjoys agile development.
Updated on August 23, 2022Comments
-
Javier Hertfelder almost 2 years
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?
-
Javier Hertfelder over 11 yearsThe problem is that Parse service encapsulates the http calls, so I don't see how to use httpBackend mock in my app, maybe I am missing the point here and I always must use httpBackend to test this kind of services..
-
AA. over 10 yearsI think your approach is good. The httpBackend should be used to test ParseService, not to test the controller. If you mock the calls to http from controller then you're violating the encapsulation. You could to add a integration test to test the true call to external server.