Error message

Deprecated function: implode(): Passing glue string after array is deprecated. Swap the parameters in drupal_get_feeds() (line 394 of /home1/tylerfra/public_html/includes/common.inc).

Angular JS - How to Unit Test a Service

Category: 

I've been working on my first Angular JS module, with the hopes I can share it with the community. Before doing that, I need to finish its implementation, and now that I've been playing around with Angular for a few weeks (and having a ton of prior JavaScript development experience), I could blast through this module's implementation very quickly.

However, I'd like to build this module "properly", and develop a unit test for each of its features along the way. After pouring over much documentation, I finally got it working... low and behold, here's how I wrote my first unit test for an Angular JS Service that uses $http (by mocking with $httpBackend):

Prerequisites

This is assuming you know how to create a simple Angular JS application, and understand the basics of Karma and Jasmine. If not, a good test run is to try the jasmine init and jasmine examples terminal commands, and get comfortable with the karma start command to be able to run some tests.

The Module

/**
 * The angular-drupal module.
 */
angular.module('my-awesome-module', []).
  service('foo', ['$http', foo]);

/**
 * The foo service for my-awesome-module.
 */
function foo($http) {
  this.bar = function() {
    return $http.get('http://example.com/rest/hello-world').then(function(result) {
        if (result.status == 200) {
          return result.data;
        }
    });
  };
}

For simplicity, we'll assume that a GET call to our RESTful API URL of http://example.com/rest/hello-world just returns a plain text string:

Hi!

The Unit Test

'use strict';

describe('foo services', function () {
    
    var foo, $httpBackend;

    beforeEach(module('my-awesome-module', function($provide) {
      // Do some other stuff before each test run if you want...
    }));

    beforeEach(inject(function (_$httpBackend_, _foo_) {
      $httpBackend = _$httpBackend_;
      foo = _foo_;
    }));

    it('foo.bar() - test', function () {
        $httpBackend.expectGET('http://example.com/rest/hello-world').respond('Hi!');
        foo.bar().then(function(data) {
            expect(data).toEqual('Hi!');
        });
        $httpBackend.flush();
    });

});

Conclusion

For me, using the $httpBackend mock is a bit strange. You'll notice that we programatically provide the "Hi!" response with our call to expectGET() in the unit test. This is because the unit test does not actually call up to the server, and instead just mocks the call for us, so we have to provide the expected response from the server. Later I hope to learn how (and I hope it is event possible) to write unit tests that use the real $http calls to a server, but alas that is for another time my coding friend.

Until then, here's the Angular module I am working on if you'd like to see a full working example.