1 October 2014

Mocking $location in angular tests

Let's say we want to mock a single service, i.e. $location. In case of internal angular's services we can use dedicated mocks, like the one described here, but often something much simpler is enough. Let's say we have a service:
angular.module('myModule').factory('myService', function($location) {
   return {
      urlSize: $location.absUrl().length
   }
});
and now we want to test it. Obviously all we need is just an object with a single function. In such case, for this test we can simply provide a new definition of the required service:
var url;

module('myModule');
module(function($provide) {
   $provide.factory('$location', function() {
      return {
         absUrl: function () {return url}
      }
   })
});
and now our new $location is registered in angular's DI container and will be provided to all dependant services. Furthermore this test is a clear documentation that myService uses only this one single function of $location. There are no spies that obfuscate behaviour of tested code. How can we use this mock? It's a closure so in every test we can change the global variable url and the mocked $location.absUrl() will return this value. Now we can simply inject the service we want to test:
inject(function(myService) {
   expect(myService.urlSize).toEqual(...);
});
Tested with angular 1.0.8 and 1.2.16