Writing Testable Application in AngularJS – Part 3


In the last post, we’d used $http service inside a controller to read JSON data which will be quite redundant once we’ll have more templates. So to avoid the redundant code, we’ll abstract it into a custom service which then can be injected in any controller.

Creating a Service

Fire up a terminal and run following command:

$ yo angular:service eywa

We’ve to modify the existing code for eywa service to suit our need. The function worship() takes a JSON file url to read the data from and returns a promise. Notice that we’ll need $http service to be injected here now.

angular.module('eShellApp').factory('eywa', function ($http) {
  return {
    worship: function (request) {
      return $http.get(request);
    }
  };
});

Now we’ll update our controller by injecting eywa and removing $http service from FibCtrl. You can see that nothing has changed but the code is now more robust and testable.

App.controller('FibCtrl', function ($scope, eywa) {
  eywa.worship('data/fib.json').then(function(data) {
    $scope.data = data.data;
  });
});

Testing a Service

In test directory, you may find a test file for the service automatically generated by yeoman generator. What is happening here is that $injector will strip leading and trailing underscores when inspecting function’s arguments to retrieve dependencies. This is useful trick since we can save the variable name without underscores for the test itself. This way we can keep a reference of the service, to use in the test, using the variable named exactly like a service in the $injector.

You can see that the $httpBackend mock allows us to specify expected requests and prepare fake responses. By using the flush() method we can simulate a HTTP response arriving from a back-end at a chosen moment. The verifyNoOutstandingExpectation method verifies that all the expected calls were made ($http methods invoked and responses flushed) while the verifyNoOutstandingRequest call makes sure that code under test didn’t trigger any unexpected XHR calls. Using those 2 methods we can make sure that the code under test invoked all the expected methods and only the expected ones. (Shamelessly copied from AngularJS Web Application Development)

describe('Service: eywa', function () {

  // load the service's module
  beforeEach(module('eShellApp'));

  // instantiate service
  var eywa, $httpBackend;
  beforeEach(inject(function (_eywa_, _$httpBackend_) {
    eywa = _eywa_;
    $httpBackend = _$httpBackend_;
  }));

  afterEach(function() {
    $httpBackend.verifyNoOutstandingExpectation();
    $httpBackend.verifyNoOutstandingRequest();
  });

  it('should read JSON data', function () {
    $httpBackend.whenGET('data/fib.json').respond('{"text":"AngularJS is [[1]].", "choices":[{"val1":"[[1]]", "val2":"awesome,powerful"}]}');

    eywa.worship('data/fib.json').success(function(data) {
      expect(data.text).toBe('AngularJS is [[1]].');
    });
    $httpBackend.flush();
  });
});

beforeEach() and afterEach() execute before and after every it() statement respectively. Notice that the request url passed to worship() method should match up with the one in whenGet().

Wrap up

In the next post, we’ll make all buttons from footer bar interact with the template. Checkout Part 4.

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