I have the following service that I want to unit test. I have mocked out the personService.deletePerson function and want to test the code inside the promise.
treeApp.factory('userMediatorService', ['$q', '$dialog', 'personService', 'confirmationMessageService',
function ($q, $dialog, personService, confirmationMessageService) {
.......
deletePerson: function (personId) {
var self = this;
personService.deletePerson(personId).then(function (data) {
//how to test this?
self.closeAndRefresh();
confirmationMessageService.set('Person Deleted');
});
},
closeAndRefresh: function () {
this.closeModal();
}
......
This is my test.
beforeEach(inject(function ($injector) {
sut = $injector.get('userMediatorService');
personService = $injector.get('personService');
q = $injector.get('$q');
def = q.defer();
spyOn(personService, 'deletePerson').andReturn(
def.promise
);
}));
describe('when a person is deleted', function () {
it('should call the delete person service and close the modal', function () {
sut.deletePerson(123);
expect(personService.deletePerson).toHaveBeenCalledWith(123); //pass
expect(sut.closeAndRefresh).toHaveBeenCalled(); //fail
});
});
Can anyone point me in the right direction for testing this kind of scenario? I realise that I can remove the spy on deletePerson, but then it wouldn't test this service in isolation.
Thanks.
Update:
I've tried using andCallFake as suggested by codemonkey and I still have the issue.
spyOn(personService, 'deletePerson').andCallFake(function () {
def = q.defer();
def.resolve(true);
return def.promise;
});
You probably want to use the callFake on the spy rather than andReturn. That will let you write a function that can resolve (or reject) the promise rather than just returning the promise.
Related
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.
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.
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
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;
}
After a week looking for a good answer/sample, I decided to post my question.
I need to know how is the best way to code and test something like this:
Controller
// my.controller.js
(function () {
'use strict';
angular.module('myApp.myModule').controller('Awesome', Awesome);
function Awesome($http, $state, AwesomeService) {
var vm = this; // using 'controllerAs' style
vm.init = init;
vm.awesomeThingToDo = awesomeThingToDo;
vm.init();
function awesomeThingToDo() {
AwesomeService.awesomeThingToDo().then(function (data) {
vm.awesomeMessage = data.awesomeMessage;
});
}
function init() {
vm.awesomeThingToDo(); // Should be ready on page start
}
}
})();
Service
// my.service.js
(function () {
'use strict';
angular.module('myApp.myModule').factory('AwesomeService', AwesomeService);
function AwesomeService($resource, $http) {
var service = {
awesomeThingToDo: awesomeThingToDo
}
return service;
function awesomeThingToDo() {
var promise = $http.get("/my-backend/api/awesome").then(function (response) {
return response.data;
});
return promise;
}
}
})();
My app works OK with this structure. And my Service unit tests are OK too.
But I don't know how to do unit tests on Controller.
I tried something like this:
Specs
// my.controller.spec.js
(function () {
'use strict';
describe("Awesome Controller Tests", function() {
beforeEach(module('myApp.myModule'));
var vm, awesomeServiceMock;
beforeEach(function () {
awesomeServiceMock = { Is this a good (or the best) way to mock the service?
awesomeThingToDo: function() {
return {
then: function() {}
}
}
};
});
beforeEach(inject(function ($controller) {
vm = $controller('Awesome', {AwesomeService : awesomeServiceMock});
}));
it("Should return an awesome message", function () {
// I don't know another way do to it... :(
spyOn(awesomeServiceMock, "awesomeThingToDo").and.callFake(function() {
return {
then: function() {
vm.awesomeMessage = 'It is awesome!'; // <-- I think I shouldn't do this.
}
}
});
vm.awesomeThingToDo(); // Call to real controller method which should call the mock service method.
expect(vm.awesomeMessage).toEqual('It is awesome!'); // It works. But ONLY because I wrote the vm.awesomeMessage above.
});
});
})();
My app uses Angular 1.2.28 and Jasmine 2.1.3 (with Grunt and Karma).
UPDATE: Solved!
it("Should return an awesome message", function () {
// Solved with callback parameter
spyOn(awesomeServiceMock, "awesomeThingToDo").and.callFake(function(callback) {
return {
then: function(callback) {
callback({awesomeMessage: 'It is awesome!'}); //callback call works fine! :D
}
}
});
I updated the question with a possible (bad) solution:
it("Should return an awesome message", function () {
// Solved with callback parameter
spyOn(awesomeServiceMock, "awesomeThingToDo").and.callFake(function(callback) {
return {
then: function(callback) {
callback({awesomeMessage: 'It is awesome!'}); //callback call works fine! :D
}
}
});
I used a callback to pass the mocked parameter and call the real implementation. :D
No, that's not how I would do this.
First, there is no need to create a mock service: you can inject the real one, and spy on it.
Second, Angular has everything you need to create promises and to resolve them. No need to create fake objects with a fake then() function.
Here's how I would do it:
describe("Awesome Controller Tests", function() {
beforeEach(module('myApp.myModule'));
var vm, awesomeService, $q, $rootScope;
beforeEach(inject(function($controller, _awesomeService_, _$q_, _$rootScope_) {
$q = _$q_;
awesomeService = _awesomeService_;
$rootScope = _$rootScope_;
vm = $controller('Awesome');
}));
it("Should return an awesome message", function () {
spyOn(awesomeService, "awesomeThingToDo").and.returnValue(
$q.when({
awesomeMessage: 'awesome message'
}));
vm.awesomeThingToDo();
// at this time, the then() callback hasn't been called yet:
// it's called at the next digest loop, that we will trigger
$rootScope.$apply();
// now the then() callback should have been called and initialized
// the message in the controller with the message of the promise
// returned by the service
expect(vm.awesomeMessage).toBe('awesome message');
});
});
Unrelated note: 1.2.28 is quite old. You should migrate to the latest version.
How can I test the value of a promise, returned by a service? In the $q documentation, the promise value is preset in the test using resolve(value). Other approaches test the service logic in a controller, using the fact that AngularJS evaluates the promises and binds the values the $scope.
In my opinion, none of these approaches actually test the logic of the service in the place where it should be tested. How can I test that the resolved promise (which is returned by the service) contains the correct value?
Here an example:
myApp.service('service', function($q){
var obj = {};
obj.test = function() {
var deferred = $q.defer();
deferred.resolve(false);
return deferred.promise;
}
return obj;
});
In order to test the service, I want to do the following in theory (which does not work in practice):
var $q, service;
beforeEach(function () {
module('myModule');
service = $injector.get('service');
});
describe('...', function() {
it('Testing whether promise contains correct value', function() {
var myPromise = service.test();
myPromise.then(function(value) {
expect(value).toBeFalsy();
});
});
});
I believe you are injecting the service in a wrong way. You have to use the underscore notation. Please refer this link. http://nathanleclaire.com/blog/2014/04/12/unit-testing-services-in-angularjs-for-fun-and-for-profit/
So, your test should look something like this.
var service;
beforeEach(function () {
module('myModule');
inject(function(_service_) {
service = _service_;
});
});
describe('...', function() {
it('Testing whether promise contains correct value', function() {
var myPromise = service.test();
myPromise.then(function(value) {
expect(value).toBeFalsy();
});
});
});
I am trying to begin writing unit tests for my angular application and hit a stopping block pretty quick as I am unsure of how exactly to mock my service in a testable way.
Is there a way to mock the REST call otherwise it would seem like I need to mirror everything within my service in my tests which doesn't seem right to me, but I am rather new to test writing so maybe this is how it is supposed to be accomplished. Any help would be greatly appreciated.
My service is as follows:
angular.module('resources.users', ['ngResource'])
.factory('User', function($resource) {
var resource = $resource('/api/index.php/users/:username', {}, {
'update': {method: 'PUT'}
});
resource.getUser = function(username, successCb) {
return resource.query({username: username}, successCb);
};
return resource;
});
My test consists thus far of:
describe('User', function() {
var mockUserResource;
beforeEach(module('resources.users'));
beforeEach(function() {
mockUserResource = sinon.stub({
getUser: function(username) {
mockUserResource.query({username: username});
},
query: function() {}
});
module(function($provide) {
$provide.value('User', mockUserResource);
})
});
describe('getUser', function() {
it('should call getUser with username', inject(function(User) {
User.getUser('test');
expect(mockUserResource.query.args[0][0]).toEqual({username: 'test'});
}));
})
});
You can mock the requests made by ngResource like this:
describe('User', function () {
var mockUserResource, $httpBackend;
beforeEach(angular.mock.module('myApp'));
beforeEach(function () {
angular.mock.inject(function ($injector) {
$httpBackend = $injector.get('$httpBackend');
mockUserResource = $injector.get('User');
})
});
describe('getUser', function () {
it('should call getUser with username', inject(function (User) {
$httpBackend.expectGET('/api/index.php/users/test')
.respond([{
username: 'test'
}]);
var result = mockUserResource.getUser('test');
$httpBackend.flush();
expect(result[0].username).toEqual('test');
}));
});
});
Demo
zsong's answer greatly helped me understand this, but I would like to expand on how it works. In case it gets edited, I list the code again here:
describe('User', function () {
var mockUserResource, $httpBackend;
beforeEach(angular.mock.module('myApp'));
beforeEach(function () {
angular.mock.inject(function ($injector) {
$httpBackend = $injector.get('$httpBackend');
mockUserResource = $injector.get('User');
})
});
describe('getUser', function () {
it('should call getUser with username', inject(function (User) {
$httpBackend.expectGET('/api/index.php/users/test')
.respond([{
username: 'test'
}]);
var result = mockUserResource.getUser('test');
$httpBackend.flush();
expect(result[0].username).toEqual('test');
}));
});
});
What's going on here?
1
beforeEach(angular.mock.module('myApp'));
We tell the Angular injector ($injector and angular.mock.inject) to inject things defined in the myApp module. You can think of it as defining a module dependency without a dependent module. Compare with how things defined in the myApp module can be injected in, say, a controller in a angular.module('myOtherApp', ['myApp']) module.
2
beforeEach(function () {
angular.mock.inject(function ($injector) {
$httpBackend = $injector.get('$httpBackend');
mockUserResource = $injector.get('User');
})
});
Before each spec, run the function ($injector) function with dependencies injected. In this case, the dependency ($injector) is resolved implicitly from the parameter name. A functionally equivalent variant of this snippet is
beforeEach(function () {
angular.mock.inject(['$httpBackend', 'User', function ($httpB, User) {
$httpBackend = $httpB;
mockUserResource = User;
}]);
});
Here we have instead declared the dependencies explicitly, and are free to use any parameter names we wish.
3
it('should call getUser with username', inject(function (User) {
Again, the test function is injected with the implicitly resolved User service as a parameter, though it isn't actually used.
Notice that this time there is no wrapper function around the inject call. inject invokes the passed function immediately if a spec is currently running, but otherwise it returns a wrapper function (see the inject docs and source code), so we don't actually need the wrapper function. Thus, we could have written the beforeEach snippet above like this:
beforeEach(angular.mock.inject(function ($injector) {
$httpBackend = $injector.get('$httpBackend');
mockUserResource = $injector.get('User');
}));
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 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();
});
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
Include angular-mocks.js in your project and read carefully through the following link.