In this article I will show how to unit test an angular service by mocking its external dependency. The service under test is trivial, but the key point is to demonstrate how to mock its asynchronous dependencies using Angular and Jasmine.
In this article I will show how to unit test an angular service by mocking its external dependencies. The service under test is trivial, but the key point is to demonstrate how to mock its asynchronous dependencies using Angular and Jasmine.
The following code shows a simple greeting service with some simple logic to create a greeting. However, the challenge is that in order to unit test the logic, we need to mock the external personService, and deal with the fact that the code is both asynchronous and promise based.
angular.module('app').factory('greetingService', ['personService', '$q', function(personService, $q){ return {greetPerson:greetPerson}; function greetPerson(id){ return personService.getPersonById(id).then(function(person){ var greeting = "Hello " + person.firstName + " " + person.lastName; var p = $q.defer(); p.resolve(greeting); return p.promise; }); } }]);
It turns out that it's relatively easy to mock service dependencies by tapping into Angular's dependency injection, and override the resolved resource in the test.
To hijack the dependency injection we can run the following setup code
beforeEach(module('app', function($provide) { $provide.service('personService', personServiceMock); }));
This code allows us to specify an arbitrary service mock wherever a personService resource is requested through Angular dependency injection. As you can see I am taking advantage of this in the test bellow:
describe('GreetingServiceTests', function(){ var greetingService; var $scope; var $q; function personServiceMock(){} beforeEach(module('app', function($provide) { $provide.service('personService', personServiceMock); })); beforeEach(function(){ inject(function ($injector) { greetingService = $injector.get('greetingService'); $scope = $injector.get('$rootScope').$new(); $q = $injector.get('$q'); }); }); it('should create a proper greeting', function(done){ personServiceMock.prototype.getPersonById = function(id){ var p = $q.defer(); p.resolve({firstName:'Joe', lastName:'Smith'}); return p.promise; }; greetingService.greetPerson(15).then(function(res){ expect(res).toBe('Hello Joe Smith'); done(); }); $scope.$digest(); }) });
By overriding the default getPersonById method I am able to define my own method where I return an already resolved promise, which takes care of mocking the promise part.
Notice, the test requires that we kick of a digest cycle at the end by calling $scope.$digest(). This is required since Angular $q needs a digest cycle in order to properly deal with the promise.