Unit testing angularjs with $http

Angularjs unit testing with $http.

How it all hangs together

When calling external servers to get or push data you can use the $http from angular.  $http has a set of methods like Get, Post and Put like you would expect.

$http callback methods.

It treats the call much like a ajax call where it has a .success method and a .error method which it returns to depending on if the call success or failure.

Scoping of parameters and .success() and .errror()

You can interact with the $scope or $rootScope to set or change data.  The scoping of the .success() and .error() means you cannot use a method level parameter and expect the value to be assigned before the the method ends.  It can go out of scope before it is guaranteed to be set.

Unit testing

In Angularjs there are 2 different kinds of $httpBackend that share the same name one for e2e testing which I may cover in another blog post and the unit one $httpBackend.  For your tests you stub out the $httpBackend.  This allows you set the expectations on how the $http service will interact while in your test.  It also prevents the unit test from attempting to call a real external service.

Methods on $httpBackend

Methods you set your expectations

The methods you interact with in a unit test on $httpBackend are of the format: whenGET(…), whenPOST(…) expectGET, expectPOST(…)

Testing and ensuring everything is in a known state.

Verification methods that are useful to unit test status

verifyNoOutstandingExpectation()

verifyNoOutstandingRequest() Use them in your unit test to ensure that your unit tests do not pollute each other by having un-flushed requests and everything is ins a known state. Example Jasmine unit test usage

describe('CustOrderCtrl', function() {
  var mockHttpBackend = null;
  var controller = null;
  beforeEach(angular.mock.inject(function($rootScope, $httpBackend, custOrderCtrl ) {
    mockHttpBackend = $httpBackend;
    controller = custOrderCtrl;
  }));
  mockHttpBackend.expectGET('/customer/1234/orders').respond([{"orderID": 1, "item": "pants"},{"orderID": 2, "item": "shirt"}]);

  afterEach(function() {
    mockHttpBackend.verifyNoOutstandingExpectation();
    return mockHttpBackend.verifyNoOutstandingRequest();
   });

//... your tests ...
  describe('get orders', function() {
      it('should have 2 orders if request customer 1234', function() {
        controller.findCustomerOrders('1234');
        mockHttpBackend.flush();
        expect(this.scope.orders).toBe(2);
      }
  }

});

Wiring in the $httpBackend

You will need to inject in the $httpBackend into your test so it can be resolved in the class and you can get a hold of it so you can stub out the calls you expect.

describe('CustOrderCtrl', function() {
  var mockHttpBackend = null;
  var controller = null;
  beforeEach(angular.mock.inject(function($rootScope, $httpBackend, custOrderCtrl ) {
    mockHttpBackend = $httpBackend;
  }
//.....
}

You can also use the $httpBackend if you use the underscore trick to inject it in. _$httpBackend_ this allows you to have an easier to read test where your parameter that you hold for the test can be called $httpBackend.

describe('CustOrderCtrl', function() {
  var $httpBackend = null;
  var controller = null;
  beforeEach(angular.mock.inject(function($rootScope, _$httpBackend_, custOrderCtrl ) {
    $httpBackend = _$httpBackend_;
  }
//.....
}

$httpBackend and Flush()

When unit testing it in necessary to call $httpBackend.flush() method so any values you have set or actions you expect to happen have been done and returned.
If you do not flush the server call before you do assertions or have methods that rely upon server interaction happening and state changing, then the data will be in the same state it was as if the call had never happened.
This can mean that there are $httpBackend.flush() calls through your unit test which has a code smell to it.  May be worth moving $http out of your controller and into a service you can stub out.

Move $http calls to a service to decouple your unit test from the server calls.

If you can, move all the $http code to another service.  Stub out the service methods that make those calls and inject that stub service into your controller unit test to make it speedier and de-coupled from your implementation.

Seeing the returned data on a .error() response with JSON data

If you are debugging a server call and you do not know why it is going badly you can use the connivence method angular.toJson(response)


.error (response) ->

console.log("error with service FOO "+ angular.toJson (response))
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s