Testing $location in an Angular service

I’ve been deep in the Angularjs world and have gone through the many emotions other developers have expressed. One thing that is lacking is best practice on testing, although yearofmoo has a huge article on testing which improves this greatly. I still had some trouble and I thought I’d post this to help others.

How do I test a service that depends on $location?

This was the very question I had trouble finding an answer. I have a service that injects $location so I can determine the environment my web app is running on.

App.factory('urlRoot', ['$location', function($location) {
     var url = 'https://api.example.com.au',
          host = $location.host(),
          suffix = /\.[\w\-]+$/.exec(host),
          filter = /\.(nz|au)/;

     suffix = (!suffix || filter.test(suffix)) ? '' : suffix[0];

     return [url, suffix, '/v1/'].join('');
}]);

Pretty simple service that injects $location inspects the host and extracts the environment e.g. .dev, .staging etc. It then returns the correct API URL to call in that environment.

In the test I needed to be able to mock the Angulars $location object so I could test different scenarios, thankfully $location depends on $browser and in tests $browser is mocked in the angular-mocks.js file allowing us to “change” the URL so $location can be tested with different values. This way $location is the real service and $browser is altering what $location sees as the current URL.

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

  var envs = [
    'http://example.com.au.dev',
    'http://example.com.au.staging',
    'http://example.co.nz.staging',
    'http://example.co.nz'
  ],
  count = 0;

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

  it('urlRoot should exist', inject(function (urlRoot) {
    expect(!!urlRoot).toBe(true);
  }));
  it('urlRoot should default to production endpoint', inject(function (urlRoot) {
    expect(urlRoot).toEqual('https://api.example.com.au/v1/');
  }));

  describe('Service: urlRoot specific environments', function() {
    beforeEach(inject(function($browser){
      $browser.url(envs[count++]);
    }));

    it('urlRoot should have ".dev" environment suffix', inject(function(urlRoot) {
      expect(urlRoot).toEqual('https://api.example.com.au.dev/v1/');
    }));

    it('urlRoot should have ".staging" environment suffix', inject(function(urlRoot) {
      expect(urlRoot).toEqual('https://api.example.com.au.staging/v1/');
    }));

    it('urlRoot should be agnostic of .co.nz domains', inject(function(urlRoot) {
      expect(urlRoot).toEqual('https://api.example.com.au.staging/v1/');
    }));

    it('urlRoot should ignore the last suffix if it\'s .nz or .au', inject(function(urlRoot) {
      expect(urlRoot).toEqual('https://api.example.com.au/v1/');
    }));
  });

});

From the code I have an array of fake environment URLs I want to test against. I use beforeEach to inject $browser, set the URL to one of the environments and using a counter variable increment it for each test. You’ll notice that I’m also injecting urlRoot for every test, this is so the beforeEach method can inject $browser and update the URL before the urlRoot service is injected and $location will then be aware of the new URL as it’s initialised after the URL has been changed.

I would love feedback from fellow Angular devs if this approach is the best way to handle a scenario like this.

Short URL: http://cssn.in/ja/046

 

Post filed under: angularjs, javascript.

Skip to comment form.

Comments are closed.