1/29/2015

Using ngTestHarness to simplify $httpBackend testing

$httpBackend is one of the greatest things that AngularJs provides. $httpBackend intercepts AJAX requests made by Angular's $http service to allow for full unit testing. The ngTestHarness helps simplify interactions with the mock backend by abstracting away WHEN and EXPECT differences. The data is passed to the call through an object so you don't have to resort to complex indented callback structures.

The ability to interrupt an AJAX call and control the response makes testing in Angular so much easier. Before addressing how the ngTestHarness handles the http mock, let's review the difference between a WHEN and an EXPECT. The difference confused me initially. The documentation provides a table which is helpful but didn't immediately click with me.

The difference can largely be understood by where you use each one. An EXPECT call is going to go inside a test. A WHEN call is probably going to go outside the individual tests and will potentially be used by all, none, or n tests. The WHEN call functions as a catch all. Any request that will be made by more than one test, and for whom you can return a generic response is covered by a WHEN. A WHEN call will not count against a test and has no requirement for being called.

Anytime that you need to test functionality that is dependent on making an AJAX call, you can use an EXPECT. An EXPECT will count against your test. The test will fail if an AJAX call is not made to the specified URL.


1. Simplifying the global WHEN

The ngTestHarness greatly simplifies the creation of a WHEN call since it is taking care of the injection for us. You can attach the WHEN function directly to the testing context instead of nestling the WHEN call inside a callback in the beforeEach.

describe('My Test Suite', function (){
  var harness = new ngTestHarness ([
    'myAngularModule'
  ]);
  
  harness.setGet({
    url: /(\/backEndUrl\/getData){1}\S*/,
    responseStatus: 200,
    responseData: {
      myData: 'test'
    },
    isWhen: true
  });
});

The test harness will now intercept all calls to /backendUrl/getData. It will return a 200 status code and the myData object. A key thing to notice is the isWhen parameter. Unless this parameter is set, the harness will make the AJAX mock an EXPECT call. Most of our testing used EXPECT over WHEN so we made EXPECT the default.


2. Simplifying the test-specific EXPECT

An EXPECT call is almost always inside a test:

describe('My Test Suite', function (){
  var harness = new ngTestHarness ([
    'myAngularModule'
  ]);
  var scope;

  ....

  afterEach(function (){
    harness.clearContext(scope);
  })

  it('Sets a scope variable once the AJAX returns', function (){
    harness.setGet({
      url: /(\/backEndUrl\/getData){1}/,
      responseStatus: 200,
      responseData: {
        myData: 'test'
      }
    });

    scope = harness.getIsolate('<my-directive></my-directive>');
    scope.updateValue();
    harness.digest(scope);

    expect(scope.returnedValue).toBe ('test');  
  });
});

This test is compiling some html and then calling a scope function called updateValue. We tell the Angular testing framework to prepare to intercept an AJAX call to 'backEndUrl/getData' before we even set up our scope. The updateValue function in the code being tested is going to make an AJAX call to 'backEndUrl/getData' and we want to control the return values.

The updateValue function is going to set the returnValue property of the scope using the myData value on the returned object. We have something definitive to test against since we are controlling the response. If the value is something other than 'test' then we know the code has a bug. The final line is stating that the test expects the returnedValue property to match the data returned in our mocked response.

Notice that in this test we are not setting isWhen to true. Therefore, the Angular testing framework will EXPECT that this URL is hit one time.

The EXPECT clause has special methods to ensure the AJAX urls have been called. We simplified this with the clearContext function. The clearContext function will call $httpBackend.verifyNoOutstandingRequest and $httpBackend.resetExpectations for you. The afterEach clause in our example is using this function to check and make sure our EXPECT is used.

These are simple examples but ngTestHarness is capable of so much more. The harness allows for the setting of response information and request information. If necessary, you can even send your own callback to be returned instead of the regular response function provided by $httpBackend. The settings are all documented here: HttpOptions.