Jasmine - How to Spy and test an AngularJS asynchronus Service - angularjs

I am trying to spy on an asynchronous service call, however so far not successful. Though the synchronus calls are able to Mock.
commonService.getResource(ResourceConst).then(function (result) {
$scope.resource = result.data;
}, function (error) {
loggerService.log(error);
});
Actual Get Resource Function
function getResource(resourceURL) {
var deferred = $q.defer();
var url = resourceURL + ".json";
$http.get(url).then(function (data) {
deferred.resolve(data);
},
function (error) {
deferred.reject(error);
});
return deferred.promise;
}
Even though I was able to spy on synchronus methods in the same service like below I was not able to do the same for this function call.
spyOn(commonService, 'hasData').and.callFake(function(args) {
return true;
});
The above code success fully mocked the synchronus call ,
$scope.hasData = commonService.hasData($scope.resource.Data);
How can I return a result
{'name': "Name"} to the asynchronous call getResource

Since the service is supposed to return a promise, return value should be mocked with a promise:
spyOn(commonService, 'hasData').and.returnValue($q.resolve({'name': "Name"} ));
The code above uses deferred antipattern. It should be instead:
function getResource(resourceURL) {
var url = resourceURL + ".json";
return $http.get(url);
}

Related

Testing async service method containing nested async method call with jasmine in AngularJS

I'm trying to test what the following method returns in it's promise (functionToTest and asyncGet are both methods defined in an angularJS service):
var functionToTest = function (param) {
var deferred = $q.defer();
asyncGet(param).then(function (result) {
//business login involving result
if (something)
return deferred.resolve(true);
else
return deferred.resolve(false);
});
return deferred.promise;
}
The unit test looks like this:
it ('should return true', function (done) {
var asyncGetResult = {};
spyOn(asyncGet).and.returnValue($q.resolve(asyncGetResult));
var param = {};
functionToTest(param).then(function (result) {
expect(result).toBe(true);
done();
});
$scope.$apply();
});
When running the test I am getting a timeout error:
Async callback was not invoked within timeout specified by
jasmine.DEFAULT_TIMEOUT_INTERVAL.
I tried putting a console.log() right after the expect() but it does not print anything, so it seems like the callback for the functionToTest(param).then() is never executed.
Any help would be appreciated.
Remove the $q.deferred anti-pattern:
var functionToTest = function (param) {
̶v̶a̶r̶ ̶d̶e̶f̶e̶r̶r̶e̶d̶ ̶=̶ ̶$̶q̶.̶d̶e̶f̶e̶r̶(̶)̶;̶
return asyncGet(param).then(function (result) {
//business login involving result
if (something)
return true;
else
return false;
});
̶r̶e̶t̶u̶r̶n̶ ̶d̶e̶f̶e̶r̶r̶e̶d̶.̶p̶r̶o̶m̶i̶s̶e̶;̶
}
Also provide an error handler:
functionToTest(param).then(function (result) {
expect(result).toBe(true);
done();
}).catch(function(err) {
console.log(err);
done();
});
Deferred anti-patterns can hang if the code is written erroneously. Avoid the anti-pattern to prevent this problem.
Include a .catch handler to see any errors.

why I get undefined from the http service? [duplicate]

This question already has answers here:
Is this a "Deferred Antipattern"?
(3 answers)
Closed 5 years ago.
I create $http service and using $q.
Here is my $http service:
function dashboardService($http, $log, $q, config) {
var service = {
getClientId: getClientIDByLayout,
};
return service;
function getClientIDByLayout(layoutId) {
var deferred = $q.defer();
return $http.get(config.baseUrl + "api/ClientLayoutMercator?layoutId=" + layoutId).then(function (result) {
deferred.resolve(result.data);
}, function (result) {
deferred.reject(result);
});
return deferred.promise;
}
}
And here is how I call service above inside controller:
dashboardService.getClientId(layoutId).then(function (data) {
var t = data;//undifined
});
But result I get in this row var t = data is undefined.
Any idea why I get undefined from the service?
Basically you have two return statement inside your getClientIDByLayout function and both are returning promise itself. As I can see with your current implementation your're creating new promise & managing rejection / resolve manually. But the problem is the 1st return statement (return $http.get() is making other return statement(return deferred.promise) redundant. Hence 1st promise returned to subscription from controller. Eventually $http.get doesn't return anything so you get undefined in successCallback of then.
You can easily fix this issue by removing 1st return statement as shown below.
function getClientIDByLayout(layoutId) {
var deferred = $q.defer();
//removed `return` from below code.
$http.get(config.baseUrl + "api/ClientLayoutMercator?layoutId=" + layoutId).then(function (result) {
deferred.resolve(result.data);
}, function (result) {
deferred.reject(result);
});
//custom promise should get return
return deferred.promise;
}
Ideally creating promise overhead considered as antipattern, rather you can utilize the promise returned by $http.get. Just return a data from its success callback to chain the promise.
Code
function getClientIDByLayout(layoutId) {
̶v̶a̶r̶ ̶d̶e̶f̶e̶r̶r̶e̶d̶ ̶=̶ ̶$̶q̶.̶d̶e̶f̶e̶r̶(̶)̶;̶
return $http.get(config.baseUrl + "api/ClientLayoutMercator?layoutId=" + layoutId)
.then(function (result) {
//returning data from promise, it will provide it to subsequent `.then`
return result.data;
}, function (error) {
͟r͟e͟t͟u͟r͟n͟ $q.reject(error);
}
);
}
Instead of using $q.defer, simply return or throw to the handler functions in the .then method:
function dashboardService($http, $log, ̶$̶q̶,̶ config) {
var service = {
getClientId: getClientIDByLayout,
};
return service;
function getClientIDByLayout(layoutId) {
̶v̶a̶r̶ ̶d̶e̶f̶e̶r̶r̶e̶d̶ ̶=̶ ̶$̶q̶.̶d̶e̶f̶e̶r̶(̶)̶;̶
return $http.get(config.baseUrl + "api/ClientLayoutMercator?layoutId=" + layoutId).then(function (result) {
̶d̶e̶f̶e̶r̶r̶e̶d̶.̶r̶e̶s̶o̶l̶v̶e̶(̶r̶e̶s̶u̶l̶t̶.̶d̶a̶t̶a̶)̶;̶
return result.data;
}, function (result) {
̶d̶e̶f̶e̶r̶r̶e̶d̶.̶r̶e̶j̶e̶c̶t̶(̶r̶e̶s̶u̶l̶t̶)̶;̶
throw result;
});
̶r̶e̶t̶u̶r̶n̶ ̶d̶e̶f̶e̶r̶r̶e̶d̶.̶p̶r̶o̶m̶i̶s̶e̶;̶
}
}
The .then method returns a new promise which is resolved or rejected via the return value of the successCallback or errorCallback (unless that value is a promise, in which case it is resolved with the value which is resolved in that promise using promise chaining).1
By erroneously returning a promise with a then method that contained functions that lacked return or throw statements the service was returning a promise that resolved as undefined.
For more information, see You're Missing the Point of Promises.

TypeError: Cannot read property 'then' of undefined angularjs-grunt test

I'm using $q service to make an async calls. I can't resolve 'then' and 'defer' in unit tests using karma.
The below is my controller code.
scope.filterUrls = [{url:'page1'}, {url: 'page2'}, {url:'page-error'}];
scope.bindFilters = function () {
angular.forEach(scope.filterUrls, function (data) {
scope.getFilterData(data.url, '').then(function (result) {
if (data.url === 'page1') {
scope.moduleData.index = result.data;
} else if (data.url === 'page2') {
scope.moduleData.page2 = result.data;
}
});
});
}
scope.getFilterData = function (filterUrls, params) {
// $q service object
var deferred = q.defer();
// regular ajax request
http({
method: 'GET',
url: app.api.root + filterUrls,
params: params
})
.success(function (result) {
// promise resolve
deferred.resolve(result);
})
.error(function (result) {
// called asynchronously if an error occurs
// or server returns response with an error status.
deferred.reject('Erreur request : ' + result);
});
return deferred.promise;
};
test spec:
it('should call getFilterData() in bindFilters()', function () {
spyOn(scope, 'getFilterData');
scope.bindFilters();
expect(scope.getFilterData).toHaveBeenCalled();
});
I'm getting an error called "TypeError: Cannot read property 'then' of undefined".
How can I write an unit test for those two methods using karma.
Update:
1.how do we can test the success and error of scope.getFilterData()
2 .then function in scope.bindFilters() function.
Please help..
If you only need to find out whether getFilterData is called or not, try returning a fake promise by faking the function:
With jasmine 1.3, we could use andCallFake:
it('should call getFilterData() in bindFilters()', function () {
spyOn(scope, 'getFilterData').andCallFake(function(){//replace with a fake function
var deferred = $q.defer(); //assume that you already inject $q service in beforeEach and save it as a variable.
return deferred.promise; //returns a fake promise
});
scope.bindFilters();
expect(scope.getFilterData).toHaveBeenCalled();
});
With jasmine 2.0, we could use and.callFake instead.
Another solution is to use andReturnValue and $q.when():
it('should call getFilterData() in bindFilters()', function () {
spyOn(scope, 'getFilterData').andReturnValue($q.when());
scope.bindFilters();
expect(scope.getFilterData).toHaveBeenCalled();
});
With jasmine 2.0, we could use and.returnValue instead.

Angular js service returns function objects when called from a controller?

var newservices = angular.module('newservices', []);
newservices.service('newservice', function ($http) {
return{
newdata: function(parameter){
return $http.get('/devicedetails/'+parameter).success(function(data) {
console.log(data)
return data
});
},
}
});
The above service is included in one of my controllers
data=newService.newdata($scope.dummy)
console.log(data)
while trying to print data what i get is $http function object as shown below
Object {then: function, catch: function, finally: function, success: function, error: function}
why is this so??
What you see is not an error. It's a Promise.
You did an $http GET request, which is asynchronous. $http.getreturns a promise that will be resolved when the remote request is completed. In that moment, you'll get the final value.
See this example, where getShops would be your method newData
this.getShop = function (id, lang) {
var promise = $http.get(appRoot + 'model/shops_' + lang + '.json');
return promise;
};
In a controller you can use it like this:
Shops.getShop($routeParams.id).then(function (response) {
console.log("data is", response.data);
$scope.shop = response.data[$routeParams.id];
});
When the data is ready, assign it to a scope.
In your case:
var data;
newService.newdata($scope.dummy).then(function (response) {
data = response.data;
});
Your service is returnig a promise
You should use some what like this, not tested though it should work.
data = newService.newdata($scope.dummy).then(function (response) {
return response.data;
},
function (error) {
return error;
});
You are using it wrong.
This work in promises. so in you controller you need to consume the promisses.
newService.newData($scope.dummy)
.then(function (data){
$scope.data = data;
console.log(data);
});
Try this.

What is the proper way to create a method with a dynamic number of parameters within an angularjs service?

I'm currently using a custom AngularJS service to enable calling remote methods on my server-side. The wrapper service is intended to simplify calling these methods in my controllers, and returns a promise object.
Within the service, i'm calling a method from a provided API that takes the following arguments: 1) the name of the remote method, 2) a variable number of parameters and 3) as last parameter the callback function that is called after completion.
So this is the function that I'm wrapping with the service:
callRemoteAPI("nameOfRemoteMethod", [param1, param2, ... , paramN,] callBack() );
The callBack function returns the promise and implements the resolve() and reject() methods.
I've currently setup different functions within the service to handle different number of parameters: callNoParam, call1Param, call2Param, etc. to be able to call different remote methods that require a different number of parameters.
This works, but obviously is not the proper Object Oriented way of doing this, and I'm sure there is a better way. But so far I've been unsuccesfull of making this work in a dynamic way.
What is the best way of handling this dynamic # of params within the angular service?
Here's the code for the service:
app.factory('remoteAction', function($q) {
return {
callNoParams: function(method, p1) {
var deferred = $q.defer();
callRemoteApi(
method,
function(result) {
if (result.succes) deferred.resolve(result);
else deferred.reject(result);
}
);
return deferred.promise;
},
call1Param: function(method, p1) {
var deferred = $q.defer();
callRemoteApi(
method,
p1,
function(result) {
if (result.succes) deferred.resolve(result);
else deferred.reject(result);
}
);
return deferred.promise;
},
call2Param: function(method, p1, p2) {
var deferred = $q.defer();
callRemoteApi(
method,
p1,p2,
function(result) {
if (result.succes) deferred.resolve(result);
else deferred.reject(result);
}
);
return deferred.promise;
},
call3Param: function(method, p1,p2,p3) {
var deferred = $q.defer();
callRemoteApi(
method,
p1,p2,p3,
function(result) {
if (result.succes) deferred.resolve(result);
else deferred.reject(result);
}
);
return deferred.promise;
}
};
// Could add more methods when needing more params,
// but 1 dynamic function would be so much nicer...
});
You can call the javascript function apply (MDN).
More on the arguments object on MDN
app.factory('remoteAction', function($q) {
return {
call: function() { //funcArgs should be an array
var deferred = $q.defer();
var parameters = arguments;
parameters.push(function(result) {
if (result.succes) deferred.resolve(result);
else deferred.reject(result);
});
callRemoteApi.apply(this,parameters);
return deferred.promise;
}
};
});

Resources