Unit tests for angular services

I’m trying to follow john papa’s excellent angular design principles article. One aspect to this is setting up routes that resolve service calls that can immediately be called by controllers using the ‘controllerAs’ syntax. However, the question arises of how to test these? After trawling round loads of different articles, I’ve cobbled together a solution that works (for better or worse).

The route

I’m in the early stages of my app so have only 1 route. In the main app.routing.js file, I’ve defined the following…

$routeProvider
 .when('/index', {
  templateUrl: 'memoryWall/main.html',
  controller: 'MemoryWall',
  controllerAs: 'vm',
  resolve: {
   memoryWallPrepService: memoryWallPrepService
  }
 })
.otherwise({
 redirectTo: '/index'
});

memoryWallPrepService.$inject = ['memoryWallService', 'youTubeService'];
function memoryWallPrepService(memoryWallService, youTubeService) {
 return {
  memoryWall: memoryWallService.memoryWall(),
  getYouTubePlayer: youTubeService.getPlayer
 };
}

So, this means that the MemoryWall controller will receive an injected ‘memoryWallPrepService’ service, so it can immediately get data it needs during initialization (see johnpapas section on services).

The service

The above ‘memoryWallService’ is responsible for getting a load of data from the back-end api using angular’s $resource service. This is (in part) what it looks like…

function memoryWallService($resource) {
 var service = {
 memoryWall: memoryWall,
  mediaTypes: mediaTypes
 };
 return service;

 function memoryWall() {
  return $resource('/web/app_dev.php/api/memorywall', {
   query: {
    method: 'GET',
    isArray: true
   }
  });
 }

Again, this is trying to follow best practise when it comes to defining services. So, as can be seen, the memoryWallPrepService is getting a reference to the memoryWallService’s ‘memoryWall’ function (a little confusing, I need to tighten up the names, sorry). This could be extended further so that the memoryWallPrepService actually gets the data and that is just passed to the memory wall (but that’s for another day).

The tests

The question is now, how to unit test the controller receiving either a mock service, or mocking the http request that the service makes. I did try mocking the service like this article shows, but I couldn’t get that working.

So, I decided that the easiest thing would be to mock the http request using angular.mock’s httpBackend object. I did the usual stuff of setting up the controllerSpec file and loading the relevant modules in ‘beforeEach’ functions. The main bit that mocks the httpRequest is here…

beforeEach(inject(function (_$httpBackend_, $controller, _memoryWallService_) {
 $scope = $rootScope.$new();
 $httpBackend = _$httpBackend_;
 memoryWallService = _memoryWallService_;

 //1
 memoryWallRequest = new RegExp('/web/app_dev.php/api/memorywall.*');
 
 //2
 $httpBackend.expectGET(memoryWallRequest)
  .respond(200, wallDataFixtures);

 spyOn(memoryWallService, 'memoryWall').andCallThrough();
 
 //3
 mockMemoryWallPrepService = {
  memoryWall: memoryWallService.memoryWall(),
   getYouTubePlayer: function () {
    return function () {
      return "mock youtube function";
    };
   }
  };
  
  //3
  vm = $controller('MemoryWall', {
   '$scope': $scope,
   'memoryWallPrepService': mockMemoryWallPrepService
  });
}));
  1. To mock the http request, you can match a specific url, a regular expression or a function. I tried matching just the url, but when ‘get’ is called, it appends the query object parameters to it. So, it was easier to just match the request against a regexp.
  2. When the memoryWallRequest is called, it is told to return a success code and some fixtures, which are known values that can be tested against. The ‘memoryWall’ function is then spied on, which means assertions can be made to check whether it has been called.
  3. The mockMemoryWallPrepService is prepared in the same way as is done in the routing config. This is then directly injected into the controller.

Finally, the following assertions can be made…

//1
it('should call the memoryWall service on init', function () {
  expect(memoryWallService.memoryWall).toHaveBeenCalled();
});

//2
it('should load data from the api on load', function () {
  expect(vm.wallData).toEqual({});
  $httpBackend.flush();
  expect(vm.wallData).toEqual(wallDataFixtures.wallData);
});
  1. This simply asserts that the ‘memoryWall’ function has been called
  2. This performs 2 assertions, one before the backend is flushed and one after. Before the request is made, the controllers ‘wallData’ object should be empty. Once the request is flushed, the wallData object should equal the fixtures object.

A side note

One problem I encountered whilst setting this up was getting karma to actually connect. I’m using the yeoman generator to scaffold the app, which uses grunt-karma to run the tests. In the grunt config, the test environment is set to run at port 9001, however in the karma.conf.js file, the port is set to 8080. Setting the karma.conf.js ‘port’ value to the same as the grunt config value got the connection working.

Conclusion

I know there are millions of similar articles out there but hopefully someone will get something from this one. As always, if there are obvious errors here or obviously better ways of doing things, please leave a comment. Thanks for reading.

Leave a Reply