3

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;
  });
};
1
  • Post the complete output from karma. And post the code of PostService. Commented May 10, 2015 at 7:25

1 Answer 1

1

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');
}));
Sign up to request clarification or add additional context in comments.

3 Comments

Thank you very much! I tried both solutions and they work. Are you more inclined to use $q.reject? I have no preference.
Yes. I have the feeling that throw should be used to signal an error (bug) rather than an expected condition. The fact that it doesn't play well with unit tests is also a sign that $q.reject() should be used instead. I also fear that configuring the exceptionHandler to log might make your tests fragile, and maybe succeed when they shouldn't.
That makes sense. Thanks once again :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.