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[];
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 = [
'https://example.com.au.dev',
'https://example.com.au.staging',
'https://example.co.nz.staging',
'https://example.co.nz'
],
count = ;
// 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.