Testing Angular Services Reply

Rick HerrmannUntil recently, the front-end testing I’d done had not directly tested any Angular services. In almost every case, the $http calls in the services were just returning data from an API back to a controller. However, on a recent project I had a scenario where the data from the API had to have some filtering and permission logic applied before passing on the data. This was a case where unit testing the service made sense.

As it turns out, testing a service is very similar to testing a controller in terms of setting up the test, injecting dependencies, and making assertions. The only real difference is the need to mock the $http call in the service. To do this the angular-mocks library provides the $httpBackend tool. Let’s walk through an example of testing a service.

As a point of reference, this is the service we are testing.  You can see that instead of just returning the data to the caller, on lines 21 and 22 we are applying some filtering.  Our test will verify that the filtering is working correctly.

 

(function() {
	'use strict';

	angular
		.module('app.services')
		.factory('SubNavigationService', SubNavigationService;

	/* @ngInject */
	function SubNavigationService($http, lodash, common, $q) {
		var service = {
			getSubNavItems: getSubNavItems
		};
		return service;

		///////////////

		function geSubNavItems(category) {
			return $http.get('json/subNavigationData.json')
				.then(function(response) {
					return lodash.chain(response.data.subnavigation)
						.filter({category:category})
						.filter(function(sn) { return common.permissions.userHasPermission(sn.id);})
						.value();
				})
				.catch(function() {
					return $q.when([]);
				});
			}
		}
	}
})();

 

And this is the spec:

 

'use strict';
describe('sub-navigation service specs', function() {
	var SubNavigationService, permissionService;
	var response, promise;

	beforeEach(module('app'));

	beforeEach(inject(function($httpBackend, _SubNavigationService_, _permissionsService_) {
		SubNavigationService = SubNavigationService_;
		permissionsService = _permissionsService_;
		response = {};
		response.subnavigation = getSubNavItems();
		$httpBackend.when('GET', 'json/subNavigationData.json').respond(response);
	}));

	afterEach(inject(function ($httpBackend) {
		$httpBackend.verifyNoOutstandingExpectation();
		$httpBackend.verifyNoOutstandingRequest();
	}));

	describe('when the user has permission to view su-nav items', function() {
		beforeEach(function() {
			sinon.stub(permissionService, 'userHasPermission').returns(true);
		});
		describe('and the category is storeviewer ', function() {
			beforeEach(inject(function($httpBackend) {
				promise = SubNavigationService.getSubNavItems('storeviewer');
				$httpBackend.flush();
		}));
		it('then there should be 2 sub nav items', function() {
			promise.then(function(result) {
				expect(result).to.have.length(2);
			});
		});
	});

The structure of the spec file looks very much like other controller tests I have written: the app module is loaded on line 6, and the dependencies are injected on line 8.  But as you can also see on line 8, we are including $httpBackend.  On line 13 we are defining the specific http call to be mocked and the data that we want to return for the test.  Note that in this case the service is reading data from a json file.  For an actual URL call the address would be used instead of the path to the json file.

Another key piece of using $httpBackend is the need “clear” the mocks after each test runs – which is what is happening in the afterEach block on lines 16-19.

On line 23 I am stubbing the call to the permission service to always return true.  This allows us to test the behavior when the user does (or does not) have permission to view the navigation items without actually calling the permission service.

On line 27 we make the actual call to our service that is under test, and on line 28 we use the flush function of the $httpBackend service to force the mocked service call to return it’s data.  This is similar to mocking a service call and having to run a digest cycle to get the promise to resolve in a controller test.

Then on line 32 we can verify our expectation just like we would in any other test.

Going forward, the services I write will most likely still not need to be tested, but in scenarios like we have here it is good to know that mocking your API calls can be handled very easily with $httpBackend.

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s