Error testing Parse Promise and $http request together - angularjs

I'm trying to test a code that waits for a Promise before calling http:
Code:
function foo() {
return Parse.Promise.as(1).then(function(one) {
return $http.get('/bar');
});
}
Test:
describe('foo', function() {
it('gets', function(done) {
$httpBackend.expect('GET', '/bar').respond(200);
foo().then(function(res) {
expect(res.status).to.be.equal(200);
done();
});
$httpBackend.flush();
});
});
Error:
1) gets
foo
No pending request to flush !
My guess is that beacuse Parse.Promise delays the promise resolution, and the http request wasn't made when the $httpBackend.flush is called.
Is there any workaround for this?

Mocha actually has promise syntax support, you can just test promises directly by dropping the done parameter and returning the promise:
describe('foo', function() {
it('gets', function() { // no `done`
try {
$httpBackend.expect('GET', '/bar').respond(200);
return foo().then(function(res) { // return here
expect(res.status).to.be.equal(200);
});
} finally {
$httpBackend.flush();
}
});
});

By default Parse Promise delays to the next tick or with timeout 0.
You can disable this behavior calling Parse.Promise.disableAPlusCompliant(); before your test.

Related

$q.all - access individual promise result?

I'm a bit new to $q angular promises. Here is my code structure:
$q.all(promises).then(function(results) {
results.forEach(function(data, status, headers, config) {
console.log(status);
});
$scope.saveDocumentCaseState();
}, function errorCallBack(response) {
console.log('error while mass update case entries');
console.log(response);
$scope.submitToWsSuccessful = 2;
});
Is there a possibility to access the result of each promise when it has been executed before the next one?
So assuming that you receive an array with an arbitrary number of promises from an angular factory called promiseFactory:
var promises = promiseFactory.getPromises(); // Returns an array of promises
promises.forEach(function(p){
p.then(promiseSuccess, promiseError);
function promiseSuccess(){
// Do something when promise succeeds
}
function promiseError(){
// Do something when promise errors
}
});
$q.all(promises).then(allSuccess, allError);
function allSuccess(){
// All calls executed successfully
}
function allError(){
// At least one of the calls failed
}

QUnit & sinon. Spying on a function called asynchronously

With Sinon, I'm trying to spy on an async function call from a function in my qunit test:
test("requestLiveCategoriesData should call parseCategoriesData", function(){
var spy = sinon.spy(this.liveCategoriesModel, 'parseCategoriesData');
this.liveCategoriesModel.requestLiveCategoriesData();
sinon.assert.calledOnce(spy);
});
The test fails (expected parseCategoriesData to be called once but was called 0 times) even though parseCategoriesData does indeed get called by the requestLiveCategoriesData - I know this because parseCategoriesData called is output to the console when I run the test in the browser
This is the code I'm testing (simplified for the sake of the question):
requestLiveCategoriesData: function () {
console.log('getting live categories');
try {
console.log("--- RETRIEVING LIVE CATEGORIES EVENTS ---");
liveCategoriesCall = new LiveEventRequest(eventObjRequest);
liveCategoriesCall.on('reset', this.parseCategoriesData, this); //the spied on function is called from here
liveCategoriesCall.fetch({
success: function (collection, resp, options) {
console.log('Live Categories Events Request complete.');
},
error: function(collection, resp) {
console.log("Error on Live Categories Events Request");
if (_.has(resp, 'statusText') && resp.statusText === "timeout") {
/* Timeout error handling */
console.log("Live Categories Events Request Timeout");
}
Conf.generalNetworkError();
},
complete: function (resp, textStatus) {
console.log("Live Categories Request teardown.");
if (liveCategoriesCall) { liveCategoriesCall.off('reset', that.parseCategoriesData, that); }
},
cache:false,
timeout: that.get('liveEventsTimeout')
});
} catch(err) {
console.log("ERROR: PROCESSING LIVE CATEGORIES");
console.log(err.message);
console.log(err.stack);
if (liveCategoriesCall) { liveCategoriesCall.off('reset', this.parseEventsData, this); }
this.set({
'lastRequest': (new Date()).getTime(),
'liveCategories': []
});
this.trigger("errorAPI", err.message);
}
},
parseCategoriesData: function (liveCategoriesCall) {
console.log('parseCategoriesData called');
},
Am I going about this the correct way?
You at least need to instruct QUnit to wait on the asynchronous response call using async().
Now when you've got that set up you need to figure out when you can call sinon.assert.calledOnce(spy);. It looks like there is currently no way to know when the LiveEventRequest has returned data.
If you have no way of modifying the current code using a setTimeout to wait a bit is your only (bad) option.
If you can change your code you should probably investigate if you can return a promise from the requestLiveCategoriesData call. Have the promise resolve when the data has arrived. Then you can wait on that promise before you do the Sinon check and follow that by a done() call like in the QUnit async documentation.
And while we're at it: You probably should use a sinon fakeserver or some other way to mock the results of the LiveEventRequest as well.

$http.get().error() does not call .then() callback

I am still trying to learn Jasmine and test an Angular service. Currently I am trying to test the .success and error calls off http.get.
Service Call
this.get = function (param1, param2) {
return $http.get('api/something/get/param1/param2')
.success(function (data) {
return data;
})
.error(function() {
return "Please select param1 AND param2";
});
};
Jasmine Tests
it('service makes unsuccessful API call', function() {
var response = "This is the response";
httpBackend.when('GET', "api/something/Get/0/0").respond(404);
var data;
service.get(0, 0).then(function(result) {
data = result;
});
httpBackend.flush();
expect(data).toEqual("Please select param1 AND param2");
});
it('service makes successful API call', function () {
var response = "This is the response";
httpBackend.when('GET', "api/something/Get/0/0").respond(response);
var data;
service.get(0, 0).then(function(result) {
data = result.data;
});
httpBackend.flush();
expect(data).toEqual(response);
});
In the first test (Error) the data = result.data line in then() is never called. On the expect(data).toEqual(), data is undefined. When I step through everything I see where the service is called and the error message is populated in result.data.
In the second test (Success), I see the same thing but the data is set when the then function is called.
Why is my then function not called on .error()?
success() and error() don't work the same way as then(). The value returned by the callback is ignored. success() and error() return the promise on which they're called, and not a new promise like then().
So, their usage should be limited to callbacks having side-effects only (like initializing a scope variable).

chaining ngResource $promise success and errors

I'd like to know if it's possible to handle the $promise returned by ngResource on multiple levels so that the code is DRY
here is a simple example
aService = function(aResource) {
var error, success;
success = function(response) {
console.log('Service Success');
};
error = function(response) {
console.log('Service Error');
};
this.servicePostReq = function() {
return aResource.save().$promise.then(success, error);
};
return this;
angular.module('app.service').factory('aService', ['aResource', aService]);
this works fine so far... it Service Success when response is OK and it Service Error when response is not OK
but when I add a controller that use this aService like following
aController = function(aService) {
var error, success;
success = function(response) {
console.log('Controller Success');
};
error = function(response) {
console.log('Controller Error');
};
this.controllerPostReq = function() {
aService.servicePostReq().then(success, error);
};
return this;
};
angular.module('app.controller').controller('aController', ['aService', aController]);
the controller always success...
so if the request return success the output is
Service Success
Controller Success
and if the request fails the output is
Service Error
Controller Success
how do I chain the promise so that I don't have to add the code handled in the service for every controller that use the service ?
The problem is your service. Change this:
this.servicePostReq = function() {
return aResource.save().$promise.then(success, error);
};
To this:
this.servicePostReq = function() {
return aResource.save().$promise.then(success);
};
Explanation:
Since your service returns aResource.save().$promise.then(success, error), it's returning a new promise with an error handler included. Later, in your controller, you add onto the chain like this.
aService.servicePostReq().then(success, error);
The complete promise chain at this point looks if you expand it out:
return aResource.save().$promise
.then(successFnFromService, errorFnFromService)
.then(successFnFromController, errorFnFromController);
Since you catch the error from aResource.save() with errorFnFromService, the promise chain is basically "clean" at this point and it will just continue with the next then.
By removing the first error handler, you allow the error to be caught later on.
A better way (in general) to handle errors in promise chains would be to use a single .catch() at the end of the chain.
Consider this bad code (try running on your browser console):
new Promise(
function(resolve, reject){
reject('first');
}).then(
function(result) {
console.log('1st success!', result);
return result;
},
function(err) {
console.log('1st error!', err);
return err;
}
).then(
function(result){
console.log('2nd success!', result);
},
function(err){
console.log("2nd error!", err);
}
);
Output:
1st error! first
2nd success! first
Better way:
new Promise(
function(resolve, reject){
reject('first');
}).then(function(result) {
console.log('1st success!', result);
return result;
}).then(function(result){
console.log('2nd success!', result);
// catch error once at the end
}).catch(function(err){
console.log("error!", err);
});
Output:
error! first
Try both of those in browser console, and change reject to resolve to see how it affects the output.
add a dependency on the $q and use $q.reject to control the execution...
in your example you need a $q.reject in the aService.error method
as mentioned here in the $q docs
reject(reason);
Creates a promise that is resolved as rejected with the specified reason. This api should be used to forward rejection in a chain of promises. If you are dealing with the last promise in a promise chain, you don't need to worry about it.
When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of reject as the throw keyword in JavaScript. This also means that if you "catch" an error via a promise error callback and you want to forward the error to the promise derived from the current promise, you have to "rethrow" the error by returning a rejection constructed via reject.
To properly chain promises, both success and error handlers should return some value. The return values are automatically wrapped in a new promise for you. This means that in the case of errors, you must return a rejected promise using $q.reject.
So your service should look like this:
aService = function($q, aResource) {
var error, success;
success = function(response) {
// important! return a value, handlers down the chain will
// execute their success handlers and receive this value
return 'Service Success';
};
error = function(response) {
// important! return a REJECTION, handlers down the chain will
// execute their error handlers and receive this value
return $q.reject('Service Error');
};
this.servicePostReq = function() {
return aResource.save().$promise.then(success, error);
};
return this;
angular.module('app.service').factory('aService', ['$q', 'aResource', aService]);

Delay an angular.js $http service

I have some angular factories for making ajax calls towards legacy ASP.NET .asmx web services like so:
module.factory('productService', ["$http",
function ($http) {
return {
getSpecialProducts: function (data) {
return $http.post('/ajax/Products.asmx/GetSpecialProducs', data);
}
}
} ]);
I'm testing on a local network so response times are "too" good. Is there a smart way of delaying the $http a couple of seconds from making the call to simulate a bad connection?
Or do I need to wrap all calls to the factory methods in a $timeout ?
$timeout(function() {
productService.getSpecialProducs(data).success(success).error(error);
}, $scope.MOCK_ajaxDelay);
Interesting question!
As you mentioned yourself, $timeout is the most logical choice for a delayed call. Instead of having $timeout calls everywhere, you could push a response interceptor that wraps the $http promise in a $timeout promise, as conceptually outlined in the documentation of $http, and register it in one of your configuration blocks. This means all $http calls are affected by the $timeout delay. Something along the lines of:
$httpProvider.interceptors.push(function($timeout) {
return {
"response": function (response) {
return $timeout(function() {
return response;
}, 2500);
}
};
});
As a bonus to your "to simulate a bad connection?", you could reject or do absolutely nothing randomly, too. Heh heh heh.
The new chrome device emulator has a network throttling function:
To get there: In Google Chrome, press F12 to open the Developer Tools. Then, on the top left corner, click the "Toggle device mode" icon (left to the "Elements" menu).
Developing more on the answer of #stevuu
responseInterceptors seems to be depreceted (as of 1.2.20) I have modified the code to work on the interceptors mechanism:
$httpProvider.interceptors.push(function($q, $timeout) {
return {
'response': function(response) {
var defer = $q.defer();
$timeout(function() {
defer.resolve(response);
}, 2300);
return defer.promise;
}
};
});
You could use the $q service for defer().promise pattern:
function someFunction(MOCK_ajaxDelay) {
var deferred = $q.defer();
$http.post('/ajax/Products.asmx/GetSpecialProducs', data).success(function(response) {
$timeout(function() {deferred.resolve({ success: true, response: response })}, MOCK_ajaxDelay);
}).error(function() {
$timeout(function() {deferred.resolve({ success: true, response: response } }, MOCK_ajaxDelay);
});
return deferred.promise;
}
someService.someFunction(500).then(function(data) {
if (data.success) {
$scope.items = data.response.d;
}
});
But if you are really mock testing, the better solution is to look into ngMock: http://docs.angularjs.org/api/ngMock.$httpBackend
While #stevuu's answer is correct, the syntax has changed in the newer AngularJS versions since then. The updated syntax is:
$httpProvider.interceptors.push(["$q", "$timeout", function ($q, $timeout) {
function slower(response) {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve(response);
}, 2000);
return deferred.promise;
}
return {
'response': slower
};
}]);
You can achieve this using the promise api combined with a $timeout. The $http.post function returns a promise from which you can call .success and .error (these are http specific methods). This promise is resolved when the http request is complete. If you build your own promise then you can tell it to delay 2 seconds and then resolve when the http request is complete:
module.factory('productService', function ($http, $q, $timeout) {
return {
getSpecialProducts: function (data) {
var defer = $q.defer();
$http.post('/ajax/Products.asmx/GetSpecialProducs', data).success(
function(data) {
// successful http request, resolve after two seconds
$timeout(function() {
defer.resolve(data);
}, 2000)
}).error(function() {
defer.reject("Http Error");
})
return defer.promise;
}
}
});
But note - you will have to use promise.then(successCallback, errorCallback) functionality - that is, you'll lose the ability to access http headers, status & config from your controllers/directives unless you explicitly supply them to the object passed to defer.resolve({})
Links:
Defer/Promise Api
Http/Promise Api
Resolve egghead video
In response to the testing aspect of your question, Fiddler has a really useful function that helps when you need to simulate delays:
Click on the AutoResponders tab in Fiddler.
Add a rule with a regex that matches the URL of the request you want to delay.
Set the "respond with" to "*delay:1000" where the number is the delay in milliseconds.
The AutoResponder functionality in Fiddler is extremely useful for testing JS that involves a lot of http requests. You can set it to respond with particular http error codes, block responses, etc.
If you are using a service that returns a promise, then inside you should put a return before the $timeout as well because that returns just another promise.
return dataService.loadSavedItem({
save_id: item.save_id,
context: item.context
}).then(function (data) {
// timeout returns a promise
return $timeout(function () {
return data;
},2000);
});
Hope it helps someone!

Resources