spyOn gives method does not exist error - angularjs

I am running a Karma test on an angular app, in the test I have the following:
return inject(function($injector) {
this.Service = {
functionWithPromise: function(postdata){
var deferred = $q.defer();
deferred.resolve({
data: {}
});
return deferred.promise;
}
};
};
and
it('should call the functionWithPromise function when the create function is called', function() {
res = {}
this.scope.create(res);
this.scope.$digest();
spyOn(Service, "functionWithPromise");
expect(this.Service.functionWithPromise).toHaveBeenCalled();
});
when I run the test it gives this error:
functionWithPromise() method does not exist
How can I get the test to recognize the functionWithPromise() function?

Figured it out, I needed to spy on this.Service instead of service, like this:
spyOn(this.Service, "functionWithPromise");

Related

How to I mock a service returning a promise in angularjs with done() and catch() callbacks

I want to mock a service for unit test in angularjs which looks something like this:
TranslationService.translate(args)
.then(function translated(value) {
//somecode
return;
})
.catch()
.done();
Following this answer:
How do I mock a service that returns promise in Angularjs Jasmine unit test?
This is what I did to mock it :
TranslateServiceMock = {
translate: jasmine.createSpy('translate').and.callFake(function() {
var deferred = $q.defer();
deferred.resolve('Remote call result');
return deferred.promise;
})};
But seems like this still doesn't work, I am guessing its because of the chained 'done' and 'catch'methods,
This is the error I get:
TypeError: undefined is not a constructor (near '....done();...'
Running out of ideas why this might be happening or how to fix this..
As mentioned in the comments, done is not a part of promise object.
I got this working by stubbing the done callback :
beforeEach( function () {
module(myModule.name, function ($provide) {
// define a .done on the $q Promise
$provide.decorator('$q', function ($delegate) {
var Promise = $delegate.when().constructor;
Promise.prototype.done = angular.noop;
return $delegate;
});
$provide.factory('TranslationService', function ($q) {
var svc = jasmine.createSpyObj('TranslationService', ['translate']);
svc.translate.and.returnValue($q.when(''));
return svc;
});
});
});

Angular Karma/Jasmine testing: How to mock a service in controller with promises?

I am trying to unit test my controller. The function that I am trying to unit test is:
function myFunction() {
MyService
.myMethod(thing1, thing2)
.then(function handleMyMethod(result) {
SomeModule.errorHandler(result)
.onSuccess(function onSuccess() {
// do stuff
})
.onError(function onError() {
// do stuff
});
});
}
Relevant test file snippet:
var MockService = {
myMethod: function(thing1, thing2) {
var promise = $q.defer().promise;
return promise;
}
};
beforeEach(module(function($provide) {
$provide.value('MyService', MockService);
}));
beforeEach(inject(function (_$controller_, _MyService_, _SomeModule_, ...) {
...
MyService = _MyService_;
MyController = _$controller_('MyController as Ctrl', {
$controller: controller,
MyService: MockService,
});
I am confused about how to write tests that allow me to hit both the onSuccess and onError cases. I am trying to cover both branches for branch coverage, but don't know how the syntax works.
You can do it one of two ways:
You can write your mock service to look at the parameters and resolve with an error or success.
myMethod:function(thing1, thing2) {
if (thing1=='all good') return $q.when('excellent');
return $q.reject('sorry, bud');
}
You can override the method closer to where you're calling it.
it('is a success', function() {
spyOn(MockService, 'myMethod').and.returnValue($q.when('excellent');
$rootScope.$apply(
MyController.doStuff('foo');
);
expect(MyController.someProperty).toEqual('excellent');
});
//etc.
Note you don't need to both override the module injector with the provide code and provide the mock service in the $controller locals parameter.

unit test for a function that returns promise in angular service

I have a service that briefly looks like this:
function service($q) {
var deferred = $q.defer();
var templateService = {
notifyChangeTemplate : notifyChangeTemplate
}
return templateService;
function notifyChangeTemplate(data) {
deferred.notify(data);
return deferred.promise;
};
}
the function notifyChangeTemplate is called in two different controllers. one of them is notifying and the other one is using the returned promise.
I am trying to write unit test for this function tried this:
it('should notify the promise', function () {
var data = { test: 1 };
var promise = myService.notifyChangeTemplate(data);
promise.then(null, null, function (response) {
var x = response;
expect(x).toBe(data);
});
$scope.$apply();
});
But it doesn't work and the "expect(x).toBe(data)" doesn't even get hit.
Can anyone give me some ideas how to test this function?

Error when testing Promise that contain .catch() methods

I have an Angular1 Controller that calls a Service which returns a promise. When adding a .catch() method to the controllers call to the service, mocha throws the following error.
TypeError: undefined is not an object (evaluating 'DogService.getDogs(_this.userId)
.then(function(result){
_this.myDogs = result;
})
.catch') in app/scripts/controllers/main.js (line 20)
init#app/scripts/controllers/main.js:20:11
test/spec/controllers/main.js:33:20
loaded#http://localhost:8080/context.js:151:17
Controller
angular.module('testProblemApp').controller('MainCtrl', ['DogService', function (DogService) {
var _this = this;
_this.myDogs = [];
_this.userId = 1;
_this.init = function(){
DogService.getDogs(_this.userId)
.then(function(result){
_this.myDogs = result;
})
.catch(function(error){
console.log(error);
});
};
}]);
Test
describe('initialze function', function () {
it('should set the myDogs array to the value returned by the Service', function () {
spyOn(DogService, 'getDogs').and.callFake(function () {
return {
then: function (callback) {
return callback([{ id: 1, name: 'baxter' }]);
},
catch: function(callback){
return callback('Error');
}
}
});
MainCtrl.init();
expect(MainCtrl.myDogs).toEqual([{ id: 1, name: 'baxter' }]);
});
});
If I remove the .catch() from the controller the test passes.
The problem here is chaining. It is expected that then will return a promise object that has catch method. While then in getDogs mock returns undefined.
It is inconvenient to mock promises or other core features with custom stubs written from scratch. $q promises can be tested with $q promises:
var dogsPromiseMock;
...
spyOn(DogService, 'getDogs').and.callFake(function () {
return dogsPromiseMock;
});
...
dogsPromiseMock = $q.resolve([{ id: 1, name: 'baxter' }]);
MainCtrl.init();
$rootScope.$digest();
expect(MainCtrl.myDogs).toEqual(...);
...
dogsPromiseMock = $q.reject();
MainCtrl.init();
$rootScope.$digest();
expect(MainCtrl.myDogs).toEqual(...);
As a rule of thumb, it is preferable to mock services fully when testing controller units, not just mock single methods.

testing .success and .error of service in controller jasmine

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;
}

Resources