Angular $httpBackend how to mock an error response? - angularjs

I'm trying to test that my program processes the error messages I get from http requests properly, but I can't figure out how to mock that with $httpBackend. Given the following:
$httpBackend.expectGET(path).respond(400, object);
400 sets response.status, and object sets response.data, but how do I set response.error? The data I need to test isn't in the data field.
Example API response:
{
status: 400,
data: {},
error: {}
}

the code ...
$httpBackend.expectGET(path).respond(400, object);
is saying when the code makes a request to the 'path' endpoint, respond with 400 status code with the response 'object'
so, for example, lets say that your unit test hits this code in your app ...
$http.GET(path).then(function(response) {
// this code is hit with 2xx responses
vm.fred = 'hello';
}).catch(function(errorResponse) {
// this code is hit with any status code not starting with 2! e.g. 400
vm.fred='failed';
});
Thus your code will cause the catch block to be executed. However, inside your unit test, to force the execution of the catch block you will need to do ...
$rootScope.$digest();
this will trigger the execution of the then or catch blocks (whichever is relevant).
you can then make expectations in your unit test as usual. Note that in your example errorResponse will be object.

respond method on $httpBackend chain doesn't offer the way to customize the response. It can be customized with $http interceptors. The most simple way to specify an error for each response is using a global:
var responseError;
beforeEach(module('app', ($httpProvider) => {
function responseErrorInterceptor($q) {
return {
responseError: (response) => {
response.error = responseError;
return $q.reject(response);
}
};
}
$httpProvider.interceptors.push(responseErrorInterceptor);
}));
...
responseError = { ... };

Related

Angularjs analogue of jQuery .always()

I want to make a HTTP request to my server and can't find the answer on my question how to make .always() like in jQuery style.
According Angular's documentation of $http, there is only this construction:
// Simple GET request example :
$http.get('/someUrl').
then(function(response) {
// this callback will be called asynchronously
// when the response is available
}, function(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
There's the finally():
$http.get('/someUrl').
then(function(response) {
// this callback will be called asynchronously
// when the response is available
}.catch(error) {
// called asynchronously if an error occurs
// or server returns response with an error status.
}).finally() {
//Always do something
});
Which will always be called no matter what the outcome is.
You can read about this in the documentation for $q here.

angularjs break forEach in $http success

I have following code in Ionic framework,
var stopScan = false;
$scope.StopScan = function() {
stopScan = true;
};
$scope.ScanContacts = function() {
Contacts.unchecked().then(function(contacts) {
var promise = $q.all(null);
angular.forEach(contacts, function(contact) {
promise = promise.then(function() {
return $http.post(apiEndpoint+'/check', {number: contact.number})
.success(function(res) {
Contacts.update(contact.id, res);
if(stopScan)
// do break loop;
})
.error(function(err) {
console.log(err);
});
});
});
});
};
It's do sending http request in loop synchronously, and break on $http error, exactly like I wanted. But how I do break the loop in the $http success? I've tried throw 'Scan stopped'; and $q.reject('Scan stopped'); but no success.
First of all, angular.forEach does not support breaking (see here and here)
Second, break statement must be directly nested within the loop, even if it was a for or while loop.
And lastly, .success is happening asynchronously, after the loop has executed, so breaking there via some other mean would have been meaningless anyway.
It seems like you expect stopScan to be set asynchronously elsewhere (for example, in response to a click from the user), but you have to decide exactly what it means to stop - does it mean "do not make any more $http.post requests", or does it mean "make all the requests, but don't not process the response?". (Your example seems to imply the latter, because you're attempting to handle it in .success, but you should know, though, that POST typically implies that changes were made on the server).
You have to understand that once you kick off an HTTP request, it's going out (or it's pending, subject to max number of connections, which is browser-dependent).
So, what you could do is fire all of the requests at once and in parallel, and then manually "timeout" ($http supports a promise-based timeout) the ones that haven't been completed:
var stopScanTimeout = $q(function(resolve){
$scope.stopScan = function(){
resolve();
}
})
var promises = [];
angular.forEach(contacts, function(contact) {
var httpPromise = $http({ method: "POST",
url: apiEndpoint+'/check',
data: {number: contact.number},
timeout: stopScanTimeout })
.then(function(response){ return response.data; },
function(error) { return {error: error};});
promises.push(httpPromise);
});
Then you could handle all the results together, and some would be "errors" (but "soft" errors) if they were not completed in time:
$q.all(promises).then(function(results){
for (var i = 0; i < results.length, i++){
var result = results[i];
if (result.error) continue;
// otherwise, process the result
Contacts.update(contact.id, result);
}
})
If you want to run with parallel HTTP requests, then go with #NewDev's answer.
However if you want to stick with serial requests, then "breaking out of the loop" couldn't be simpler.
All you need to do is throw, which won't break as such but will send the constructed promise chain down its error path. At the stop point, there will be no unreturned requests and no more requests will be sent.
I would write something like this, using contacts.reduce(...) to build the chain.
$scope.ScanContacts = function() {
return Contacts.unchecked().then(function(contacts) {
return contacts.reduce(function (p, contact) {
return p.then(function() {
return $http.post(apiEndpoint + '/check', { number: contact.number })
.then(function(res) {
if(stopScan) throw new Error('scan stopped');
Contacts.update(contact.id, res);//you can choose to service the last response or not but placing this line above or below the throw line.
}, function(err) {
// As the second .then param, this callback will catch any http errors but not the 'scan stopped' error.
// By catching http errors, the scan will be allows to continue.
// To stop on http error, either remove this callback or rethrow the error.
console.log(err);
});
});
}, $q.when());
});
};
Here's evidence that throwing will give the required "stop" effect.
If throwing doesn't work in the real code, then it would seem that something else is wrong.

catch errors/read status code from rest call - angular

How do you catch errors/read http status code when making a rest call in one of these formats, both work to return a successful response, just no idea how to grab the info i need. I can get the object with values returned as I need, I just cant get the http status code.
methods provided by #Claies in a response to this question (Get data from $resource response in angular factory)
$scope.makeRestCall= function () {
$scope.member = Item.makeRestCallWithHeaders('123456789', '789456123')
.query().$promise.then(function(response){
});
};
$scope.makeRestCall= function () {
$scope.member = Item.makeRestCallWithHeaders('123456789', '789456123')
.query({}, function() {
})
};
I have tried to use the first method here and grab something from the function(response) such as response.status, but it returns undefined.
For reference, using this factory:
.factory("Item", function($resource) {
var endpoint = "http://some valid url";
function makeRestCallWithHeaders(id1, id2) {
return $resource(endpoint, null, {
query: {
method: 'GET',
headers: {
'id1': id1,
'id2': id2
}
}
})
}
var item = {
makeRestCallWithHeaders: makeRestCallWithHeaders
}
return item ;
})
Item returns something like this:
{firstName:Joe, lastName:smith}
I am really just trying to figure out how I can access the status code returned by the REST call. Absolute end goal is to read any error response and return error to UI written in angular as well. If there is a way to just read this in the UI, that could work too.
To read the error status you need to pass in the errorCallback to the $promise:
$scope.makeRestCall= function () {
$scope.member = Item.makeRestCallWithHeaders('123456789', '789456123')
.query().$promise.then(
function(response){
//this is the successCallback
//response.status & response.statusText do not exist here by default
//because you don't really need them - the call succeeded
//see rest of answer below if you really need to do this
// but be sure you really do...
},
function(repsonse) {
//this is the errorCallback
//response.status === code
//response.statusText === status text!
//so to get the status code you could do this:
var statusCode = response.status;
}
);
};
You shouldn't need the status in the successCallback, because it is a success and you know the success code implicitly.
Therefore the status is not available in the successCallback by default.
If, for some reason, you do need the status in your successCallback, you could write an interceptor to put this information somewhere, but be aware that the angular framework deals with the data differently in different success scenarios so you will need to write code for different cases.

angular $resource receive extra information

I am using ng-resource to do ajax request. I want to send extra info besides the data.
For example, I have an article entity on my server
exports.fetchArticle = function(req, res, next) {
var article = req.article
return res.json({data: article, message: 'success fetch article'})
}
The reason I wrap it is that, in the case of deletion, it makes no sense to send data, I can just return res.json({data: null, message: 'deleted successfully'})
on my client side, I have:
$scope.fetchArticle = function() {
Article.get({articleId: $routeParams.articleId}, function(response) {
$scope.article = response.data
$scope.ajaxSuccess = response.message
}, function(err) {
$scope.ajaxError = err.data.message
})
}
$scope.article is not an instance of ng-resource anymore, thus I can't do further request with $scope.article, i.e. this will cause error, since $scope.article is a plain json object:
$scope.article.$update(function(response) {...})
If I simply return res.json(article) from server, it works, but I can't send along the message.
The reason I dont generate the message from client but fetch from server is that, the error message is from server, I want to keep success message consistent with the error message.
Is there any other elegant way to send the message?
Assuming that all your servers responses follow this format:
{
data: {/*...*/},
message: 'some message'
}
You could use $http's transformResponse for that, so that you get an ngResource instance that is your returned object while still processing your message. For that, you need a transform-function:
function processMessage(data, message) {
//Do whatever you want with your message here, like displaying it
}
function transform(response) {
processMessage(response.data,response.message);
var data = response.data;
delete response.data;
delete response.message;
for(var attributeName in data) {
response[attributeName] = data[attributeName];
}
return response;
}
Then you can add this function to $http's default transfroms in the config of your app:
angular.module("yourApp",[/* ... */])
.config(function($httpProvider){
//....all your other config
$httpProvider.defaults.transformResponse.unshift(transform);
});
Now all repsonses from $http get transformed by this function, triggering processMessage and leaving you with a ngResource instance of the returned object.

Angular test using $httpBackend fails with "400 thrown" error

For hours I've been trying to test my NewPostController with $httpBackend. The problem is whenever I set non-2xx status code in the response, the test fails.
NewPostController has the following method:
$scope.submit = function () {
var newPost = $scope.newPost;
PostService.insertPost(newPost).then(function (post) {
$location.path("/my-posts");
}, function (status) {
$scope.form.$setPristine();
$scope.error = status;
});
};
I have a problem testing the failure path:
it(...) {
...
$scope.post.text = "";
$httpBackend.expectPOST("/create-post", {"post":$scope.post}).respond(400);
$scope.submit();
$httpBackend.flush();
expect($scope.error).toBeDefined();
$scope.post.text = "This is a valid text.";
$httpBackend.expectPOST("/create-post", {"post": $scope.post}).respond(200);
$scope.submit();
$httpBackend.flush();
expect($location.path()).toBe("/my-posts");
});
The test fails with a message "400 thrown" (no callstack). I tried to change the order of subtests, use whenPOST instead of expectPOST and combine the methods as they do in Angular docs (https://docs.angularjs.org/api/ngMock/service/$httpBackend) but without success.
Please help.
EDIT:
Now when I look at PostService, it makes sense where the "400 thrown" comes from but I expected the error to be handled by angular. I threw it because of the section "Handling problems in nested service calls" of this article. It is supposed to be a shorter version of deferred.resolve/reject mechanism.
this.insertPost = function (newPost) {
return $http({
method: "post",
url: "/create-post",
data: {
post: newPost
}
}).then(function (res) {
return (res.data);
}, function (res) {
throw res.status;
});
};
This is indeed strange, and is perhaps something the angular team didn't consider.
When a promise is rejected by throwing (as you're doing), the angular $exceptionHandler service is called with the thrown exception. By default, this service just logs the exception in the browser console.
But when using ngMocks, this service is replaced by a mock implementation that can either log or rethrow the exception. The default mode is to rethrow, in order to make a test fail when an exception is thrown.
My advice would be to avoid using throw to simply reject a promise, and thus replace
function (res) {
throw res.status;
}
by
function (res) {
return $q.reject(res.status);
}
But if you really want to keep using throw, you can also configure the mock exceptionHandler to log instead of rethrowing:
beforeEach(module(function($exceptionHandlerProvider) {
$exceptionHandlerProvider.mode('log');
}));

Resources