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

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

Related

AngularJS Nondescript Error at "return logFn.apply(console, args);"

I'm using AngularJS v1.5.0. I'm on Chrome Version 55.0.2883.95. The error I'm seeing shows similar failure behavior to this SO post, although the error description for my situation just states Object.
I've created a plunker to demonstrate the error. Open the developer console at the plunker to see the resulting error.
Given the following service,
this.test = function() {
return $q(function(resolve, reject) {
var errorObject = {
httpStatusCode: 503,
error: {
code: 5030,
message: 'Oh no! Something went wrong, please try again'
}
};
reject(errorObject);
}).then(function successCallback(response) {
return response;
}).catch(function errorCallback(response) {
throw response;
});
};
AngularJS code generates the following error:
angular.js:13550 Object {httpStatusCode: 503, error: Object}
The AngularJS code in play is:
function consoleLog(type) {
var console = $window.console || {},
logFn = console[type] || console.log || noop,
hasApply = false;
// Note: reading logFn.apply throws an error in IE11 in IE8 document mode.
// The reason behind this is that console.log has type "object" in IE8...
try {
hasApply = !!logFn.apply;
} catch (e) {}
if (hasApply) {
return function() {
var args = [];
forEach(arguments, function(arg) {
args.push(formatError(arg));
});
return logFn.apply(console, args); // Error thrown on this line
};
}
// we are IE which either doesn't have window.console => this is noop and we do nothing,
// or we are IE where console.log doesn't have apply so we log at least first 2 args
return function(arg1, arg2) {
logFn(arg1, arg2 == null ? '' : arg2);
};
Here's how I call the service:
var test = function() {
userService.test()
.then(function successCallback(responseObject) {
console.log('Beginning of then');
})
.catch(function errorCallback(errorResponseObject) {
console.log('Beginning of catch');
});
}
The error seems to be caused by the fact that I am handling the promise rejection, and then re-throwing it. If I don't catch and rethrow, I don't get the error. Why do I receive the error when catching and re-throwing?
Update: It appears that using the $q service to reject the caught promise rejection avoids the AngularJS error I was seeing. I'll use that approach for now, but would still be interested to know why throwing out of the promise catch generates the error.
Example code without the error:
this.test = function() {
return $q(function(resolve, reject) {
var errorObject = {
httpStatusCode: 503,
error: {
code: 5030,
message: 'Oh no! Something went wrong, please try again'
}
};
reject(errorObject);
}).then(function successCallback(response) {
return response;
}).catch(function errorCallback(response) {
return $q.reject(response);
});
};
This question has nothing to do with AngularJS and much more on how promises work (including AngularJS's $q).
Throwing in a .catch is bound to have issues. Axel has an excellent explanation
if you want a quick and dirty method to get an exception to the console(or other logging mechanisms) you can use this trick:
.catch((err) => setTimeout(() => throw err));
Or its es5 variant:
.catch(function (err) { setTimeout(function () {throw err},0)})
This will keep the error as is, and get it out of the promise chain, without changing it.
However, I think it's better to incorporate the way that Axel explains in his article.
I've found what I believe to be the answer to my question. Buried in this discussion on the AngularJS Github issues section, #gkalpak notes the following:
The only difference between them is that return $q.reject(anything) will just pass anything down the chain, while throw anything will additionally pass anything to the $exceptionHandler. Other than that, both methods work the same.
So, the issue as far as I understand it, is that the $exceptionHandler prints the exception to the console. Using $q.reject as I stated in my update and again below does avoid this behavior and is my recommended solution to avoiding the console error.
this.test = function() {
return $q(function(resolve, reject) {
var errorObject = {
httpStatusCode: 503,
error: {
code: 5030,
message: 'Oh no! Something went wrong, please try again'
}
};
reject(errorObject);
}).then(function successCallback(response) {
return response;
}).catch(function errorCallback(response) {
return $q.reject(response);
});
};
Update - Based on #Sanders-Eias answer below, it is bad practice to throw exceptions out of async functions in general. That statement further bolsters the $q.reject approach.

(intermediate value).error is not a function - AngularJs

I'm calling a method inside WebApi 2 controller everything is working as expected however when I check the developer console I see the following error message:
Error: (intermediate value).error is not a function
Now I've done the research and some people have said: You're missing a closing semi colon within your method, however I've gone through my code and I can't see anywhere that I'm missing this.
My Angular Js login function:
$scope.Login = function () {
var emailAddress = $scope.emailAddress.trim();
var password = $scope.password.trim();
var valid = loginApi(emailAddress, password);
};
Which calls this function:
function loginApi(emailAddress, password) {
var login = { "EmailAddress": emailAddress, "Password": password };
$http.post("/api/Login/", login).success(function (data, status) {
alert('success');
}
.error(function (error, status) {
alert(error);
}));
};
I may be wrong but don't you need a closing parenthesis after your .success function and then remove one at the end of the .error?
$http.post().success(function() {}).error(function() {});
Otherwise it would seem like you're enclosing your error in your success.
The other thing to think about is that in Angular both .success and .error have been deprecated and they suggest using then:
Angular API: $http
The $http legacy promise methods success and error have been deprecated. Use the standard then method instead.

Angular $httpBackend how to mock an error response?

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 = { ... };

Breeze EntityManager.executeQuery() not returning promise (or data?) in AngularJS

I am trying to use Breeze to get data from a server into an AngularJS app, and although the server is sending the JSON data, the client app is not getting it. The closest I've gotten to identifying the issue using the debugger is to see that the following function getRemoteEntities(), which is part of a Factory, should return a promise but instead returns an empty Object {} when called with a valid entityURL and jsonAdapter:
[...]
var manager = entityManagerFactory.newManager();
[...]
return {
getRemoteEntities: function (entityUrl, jsonAdapter) {
var query = breeze.EntityQuery
.from(entityUrl)
.using(jsonAdapter);
return manager.executeQuery(query)
.then(function (results) {
return results;
})
.catch(function (error) {
return $q.reject(error);
});
}
}
I have checked, and the code does use the Breeze Angular Service as described here. I do not understand what is not working.
EDIT: Removing the .using(jsonAdapter) means that I am able to get and resolve the promise, suggesting that it might be doing something that messes it up. Here is an example (they all follow this pattern):
.value('jsonProfileResultsAdapter', new breeze.JsonResultsAdapter({
name: "xyz", // mild obfuscation
extractResults: function (data) {
var results = data.results;
if (!results) throw new Error("Unable to resolve 'results' property");
return results;
},
visitNode: function (node, parseContext, nodeContext) {
if (node) {
if (node.person && node.assignments) {
return {entityType: "EmployeeModel"}
}
}
}
}))
What is the jsonAdapter doing? That's an unusual feature (not wrong, just unusual). Maybe you're doing something inside it that blows up the promise.
Take it away and see what you get. If you get a promise, even a failed promise, then you're on to something.

Callback failing even though the statuscode is 200

I have an odata, breeze, angular application.
Please see here: http://bepozreports.azurewebsites.net/#/dashboard
You will notice an alert that I have setup which just says the callback has failed.
If you navigate to app/js/controllers.js you will see this code
accountFactory.getAll()
.then(successCallback)
.catch(failCallback);
The failCallback always is called even though the call to http://bepozreports.azurewebsites.net/odata/Accounts?$orderby=FirstName results in a correct response??
Any ideas?
If you debug your app and look at line 15199 of breeze.debug.js, you will find the following code:
OData.read({
requestUri: url,
headers: { "DataServiceVersion": "2.0" }
},
function (data, response) {
var inlineCount;
if (data.__count) {
// OData can return data.__count as a string
inlineCount = parseInt(data.__count, 10);
}
return deferred.resolve({ results: data.results, inlineCount: inlineCount });
},
function (error) {
return deferred.reject(createError(error, url));
});
The error callback is triggered here with your url, and the error response is message: "no handler for data"
A failure at good error reporting by breeze.
After doing a little bit of digging (I'm not familiar with datajs), it looks like the problem might have something to do with your CORS setup (or lack thereof). Hope that helps.

Resources