Im using angularjs 1.4 and jasmine 2.4.
Im trying to test a function and I want to make it return a promise, so another layer above it can deal possible values.
My issue is that the function first validate the inputs. If they are not the right ones I want to return a rejected promise. Otherwise it will do whatever it has to do and resolve the promise.
Here is part of the function from the emailSvc in question:
// Function found in the emailSvc
this.sendEmail = function sendEmail(apiKey, token, email_type)
{
// Prerequisite to send email
if(!apiKey) {
return $q.when("apiKey not present.");
}
var deferred = $q.defer();
// Ajax call
serviceApiEmail.send(apiKey, token, email_type)
.then(function(data){
deferred.resolve(data);
})
.catch(function(e){
deferred.reject(e);
})
return deferred.promise;
}
And my test case is like follow:
it('should reject sending email if apiKey is not present', function(){
var rejectEmail;
var apiKey,
verifyToken = acceptedVerifyToken,
emailType = const_EMAIL_TYPE.SIGNUP;
i_emailSvc.sendEmail(apiKey, verifyToken, emailType)
.then(function(){
// It should not come here
rejectEmail = false;
}).catch(function(){
rejectEmail = true;
});
// It comes here without executing any success or fail promise handlers
expect(rejectEmail).toBe(true);
});
The issue is that when rejecting the promise the catch is never executed. I believe this is with a misconception I have with promises.
Any ideas why the catch and then are not working in here?
You are resolving the promise if the apiKey is not present when using $q.when(). This would indicate a successfully completed promise. To indicate failure you should use $q.reject i.e.
if(!apiKey) {
return $q.reject("apiKey not present.");
}
Additionally for the then or catch callbacks to be executed in your test you would usually need to trigger a digest cycle. The usual way of doing this is tests is to get a reference to the $rootScope and call $rootScope.$digest().
Are you mocking the serviceApiEmail.send method? Are you reaching the then at all? I'd check for something else then rejectEmail boolean because with Javascript your test is going to be false even if the 'then' part is not reached, so you aren't maybe even reaching the promise resolve part
Related
I am struggling with chaining promises using $timeouts. I would like to have a "$timeout(myFunction,1000).then()" function that fires only when ALL chained timeouts returned by myFunction are resolved.
This code snippet contains different stuff I tried and I would like to achieve:
$timeout(myFunction,1000).then(function(myPromise) {
console.log("I would like this message to appear when ALL chained promises are resolved, without knowing in advance how many chained promises there are. In this case this would be after 1500 ms, not 1000ms")
myPromise.then(function()) {
console.log("with this code patern I get a message after 1500ms, which is what I want, but this does not work anymore if myOtherFunction would return a third chained $timeout")
}
})
myFunction = function() {
console.log("hi, i am launching another timeout")
return $timeout(myOtherFunction, 500)
}
myOtherFunction = function () {
console.log("1500 ms have passed")
}
How should I fix my code? Thanks!
Return promises to the success handler:
$timeout(null,1000).then(function() {
console.log("It is 1000ms");
var delay = 500
return myPromise(delay);
// ^^^^^^ return promise for chaining
}).then(function() {
console.log("this happens after myPromise resolves");
});
function myPromise(delay) {
promise = $timeout(null, delay);
return promise;
});
Because calling the .then method of a promise returns a new derived promise, it is easily possible to create a chain of promises. It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.
-- AngularJS $q Service API Reference -- Chaining promises;
Inspired by the answer of georgeawg I created my custom timeout function that returns the promise returned by fct, instead of the promise returned by $timeout. I did this to keep the $timeout syntax.
vm.customTimeout = function (fct, timeout){
return $timeout(fct, timeout).then(function(myReturnedPromise){
return myReturnedPromise
});
}
This function is sufficient to solve my problem above. I can chain as much customTimeouts I want.
Example :
vm.customTimeout(myFunction,1000).then(function() {
var activity1 = anyFunctionReturningAPromise(100);
var activity2 = anyFunctionReturningAPromise(1000);
return $q.all([activity1, activity2])
console.log("Without knowing the content of myFunction, I am 100% sure that
every single chained promise retuned by myFunction is resolved before
executing this code, which is quite nice!")
}).then(function(){
console.log("executes when customTimeout, activity1 & activity2 are all resolved.")
})
anyFunctionReturningAPromise = function(delay) {
return vm.customTimeout(myFunction, delay)
}
Feel free to comment what you think of it.
I hope this will be useful for someone else :)
I have a following piece of code:
function getData(foos, bars) {
var generated = {};
return $q(function(resolve, reject) {
var promises = _.map(foos, function(foo) {
return _.map(bars, function(bar) {
return someServiceCall(foo, bar).then(function(data) {
_.set(generated[foo.id], player.id.toString() + '.data', data);
});
});
});
// Join all promises in to a final resolve
$q.all(promises).then(function() {
resolve(generated);
}, reject);
});
}
What I want to achieve is to have all the someServiceCall-s and it's success handlers finished by the time resolve(generated) is called. When I debug this part of the code in the developer toolbar, resolve(generated) is called before the success handler of someServiceCall is called for every promise.
Note: This not always breaks the functionality, as the objects are passed as a reference, so the data is set even if resolve was already called, but I think the functionality would be clearer if all those calls were finished by the time resolve is called.
I just realized my dumb mistake. The two nested maps resulted the following structure:
var promises = [[Promise, Promise][Promise, Promise, Promise]];
While $q.all expects an array of promises:
var promises = [Promise, Promise, Promise, Promise, Promise];
I could easily solve this by replacing the first map call by flatMap:
return _.flatMap(bars, function(bar) {
It's still strange for me that $q.all silently just resolved the promise without an error or warning that the data format is not appropriate.
I hope I can help someone who runs into this problem in the future.
I still can't understand the role of using the $q service, (what exactly will it add) if you want to create a service that need to call only one API via http , in this situation I don't know why shouldn't I just do the following (without using $q) :
this.getMovie = function(movie) {
return $http.get('/api/v1/movies/' + movie)
.then(
function(response) {
return {
title: response.data.title,
cost: response.data.price
});
},
function(httpError) {
// translate the error
throw httpError.status + " : " +
httpError.data;
});
};
Very good question and one very few people appreciate the answer to.
Consider this:
this.getMovie = function(movie) {
return $http.get('/api/v1/movies/' + movie);
};
Great code but these rules apply:
$http will resolve for 2xx responses and will otherwise reject. sometimes we don't want this. we want to reject on resolution and resolve on rejection. This makes sense when you consider a HEAD request to check the existence of something.
A HEAD request to /book/fred returning 200 shows book fred exists. But if my function is testing whether book fred is unique, it is not and so we want to reject on a 200. this is where $q comes in. Now I can do this:
var defer = $q.defer();
$http.head('/book/fred').then(function(response) {
// 2xx response so reject because its not unique
defer.reject(response);
}).catch(function(failResponse) {
defer.resolve(failResponse);
});
return defer.promise;
$q gives me total control of when I reject AND when I resolve.
Also, $q allows me to reject or resolve with any value. So if I am only interested in some of the response I can resolve with just the bit I am interested in.
Finally, I can use $q to turn non-promise based code into a promise.
var a = 5;
var b = 10;
var defer = $q.defer();
var resolve(a+b);
return defer.promise;
Bosh, if I need a promise as my return value then I've got one.
This is also great when mocking for unit tests.
AngularJS services such as $http, $timeout, $resource, etc. use the $q service internally to generate their promises. With those services there generally is no need to inject the $q service. In fact if you see $q.defer being used with those services, you should be asking Is this a “Deferred Antipattern”?.
There are some methods of the $q service that are useful in certain circumstances.
The $q.all method is used to wait on several promises.
var promise1 = $http.get(url1);
var promise2 = $http.get(url2);
$q.all([promise1, promise2]).then( responseArray ) {
$scope.data1 = responseArray[0].data;
$scope.data2 = responseArray[1].data;
}).catch( function ( firstError ) {
console.log(firstError.status)
});
The .catch method can be used to convert a rejected promise to a fulfilled promise. And vice versa with the .then method. No need to use $q.defer for that. For more information, see Angular execution order with $q.
The $q.when method is useful for generating a promise from unknown sources.
var promise = $q.when(ambiguousAPI(arg1));
The $q.when method creates a $q promise in all cases whether ambiguousAPI returns a value, a $q promise, or a promise from another library.
Because calling the .then method of a promise returns a new derived promise, it is easily possible to create a chain of promises. It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.1
To summarize: The $q service is used to create a promise, so when using service (like $http,$timeout,$resource,etc.) that already return promises you generally don't need to use the $q service.
In this case you certainly don't need it because $http.get itself returns a promise. But for example if you perform async call only on some condition it is useful
function acyncService () {
if (dataLoaded) return $q.resolve(data);
return $http.get('path/to/load/data');
}
In this case even if you do not perform async call, you still can use
acyncService().then(function(data){
console.log(data);
});
This is only one of many examples. It is also useful to use $q promises when you do async requests with other libs like AWS SDK for instance.
$http does an asynchronous call. So, ideally if you want to get the value of the response in the url, you should use a '$scope' variable to store it.
$http("your url").then(function(response){
//This is the success callback
$scope.movies = response.data;
});
It seems that promises do not resolve in Angular/Jasmine tests unless you force a $scope.$digest(). This is silly IMO but fine, I have that working where applicable (controllers).
The situation I'm in now is I have a service which could care less about any scopes in the application, all it does it return some data from the server but the promise doesn't seem to be resolving.
app.service('myService', function($q) {
return {
getSomething: function() {
var deferred = $q.defer();
deferred.resolve('test');
return deferred.promise;
}
}
});
describe('Method: getSomething', function() {
// In this case the expect()s are never executed
it('should get something', function(done) {
var promise = myService.getSomething();
promise.then(function(resp) {
expect(resp).toBe('test');
expect(1).toEqual(2);
});
done();
});
// This throws an error because done() is never called.
// Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
it('should get something', function(done) {
var promise = myService.getSomething();
promise.then(function(resp) {
expect(resp).toBe('test');
expect(1).toEqual(2);
done();
});
});
});
What is the correct way to test this functionality?
Edit: Solution for reference. Apparently you are forced to inject and digest the $rootScope even if the service is not using it.
it('should get something', function($rootScope, done) {
var promise = myService.getSomething();
promise.then(function(resp) {
expect(resp).toBe('test');
});
$rootScope.$digest();
done();
});
You need to inject $rootScope in your test and trigger $digest on it.
there is always the $rootScope, use it
inject(function($rootScope){
myRootScope=$rootScope;
})
....
myRootScope.$digest();
So I have be struggling with this all afternoon. After reading this post, I too felt that there was something off with the answer;it turns out there is. None of the above answers give a clear explanation as to where and why to use $rootScope.$digest. So, here is what I came up with.
First off why? You need to use $rootScope.$digest whenever you are responding from a non-angular event or callback. This would include pure DOM events, jQuery events, and other 3rd party Promise libraries other than $q which is part of angular.
Secondly where? In your code, NOT your test. There is no need to inject $rootScope into your test, it is only needed in your actual angular service. That is where all of the above fail to make clear what the answer is, they show $rootScope.$digest as being called from the test.
I hope this helps the next person that comes a long that has is same issue.
Update
I deleted this post yesterday when it got voted down. Today I continued to have this problem trying to use the answers, graciously provided above. So, I standby my answer at the cost of reputation points, and as such , I am undeleting it.
This is what you need in event handlers that are non-angular, and you are using $q and trying to test with Jasmine.
something.on('ready', function(err) {
$rootScope.$apply(function(){deferred.resolve()});
});
Note that it may need to be wrapped in a $timeout in some case.
something.on('ready', function(err) {
$timeout(function(){
$rootScope.$apply(function(){deferred.resolve()});
});
});
One more note. In the original problem examples you are calling done at the wrong time. You need to call done inside of the then method (or the catch or finally), of the promise, after is resolves. You are calling it before the promise resolves, which is causing the it clause to terminate.
From the angular documentation.
https://docs.angularjs.org/api/ng/service/$q
it('should simulate promise', inject(function($q, $rootScope) {
var deferred = $q.defer();
var promise = deferred.promise;
var resolvedValue;
promise.then(function(value) { resolvedValue = value; });
expect(resolvedValue).toBeUndefined();
// Simulate resolving of promise
deferred.resolve(123);
// Note that the 'then' function does not get called synchronously.
// This is because we want the promise API to always be async, whether or not
// it got called synchronously or asynchronously.
expect(resolvedValue).toBeUndefined();
// Propagate promise resolution to 'then' functions using $apply().
$rootScope.$apply();
expect(resolvedValue).toEqual(123);
}));
I have my application that should open a popup ask a confirmation at the user, then make an ajax cal and close the popup.
I tried to do it using a chain of promise (I've already used it, and I remember that it should work in this way), but it seems to block after the call toreservationService.confirm($scope.object);. Now it is a fake service implemented with a setTimeout and $q just to return a promise (in future it will make the ajax call). Is this a valid code or I didn't undestood how promise works?
For the popup I choose AngularUI and the code is this:
reservationService.book($scope.object, day)
.then(function(){
var dialogOpts = {/* dialog options omitted*/}
return $dialog.dialog(dialogOpts).open();
})
.then(function(result){
console.log('confirmed? ', result);
if (result){
//After this line it doesn't do nothing, also if the promise is resolved
return reservationService.confirm($scope.object);
}
})
.then(function(){
//this function is never executed
$scope.$emit('object:detail',{object: $scope.object});
});
reservationService:
function confirm(){
var deferred = $q.defer();
setTimeout(function(){
console.log('Confirming');
deferred.resolve(true)
}, 500);
return deferred.promise;
}
SOLVED
change setTimeout with $timeout angular's service
Used $timeout instead of setTimeout 'cause it works togheter at the angular scope, forcing the digest phase (or use $scope.apply() inside the setTimeout).
Can you try
//skipping the first then
.then(function(result){
var deferred = $q.defer();
console.log('confirmed? ', result);
if (result){
//After this line it doesn't do nothing, also if the promise is resolved
return deferred.resolve(reservationService.confirm($scope.object));
}
deferred.resolve();
return deferred.promise;
})
.then(function(){
//this function is never executed
$scope.$emit('object:detail',{object: $scope.object});
});
For chaining then, the last then success or failure function should return a promise. As the $q documentation mentions
then(successCallback, errorCallback) – regardless of when the promise
was or will be resolved or rejected, then calls one of the success or
error callbacks asynchronously as soon as the result is available. The
callbacks are called with a single argument: the result or rejection
reason.
This method returns a new promise which is resolved or rejected via
the return value of the successCallback or errorCallback.