How do I test an AngularJS service with Jasmine?

Robert picture Robert · Oct 22, 2012 · Viewed 77.3k times · Source

(There is a related question here: Jasmine test does not see AngularJS module)

I just want to test a service without bootstrapping Angular.

I have look at some examples and the tutorial but I am not going anywhere.

I have just three files:

  • myService.js: where I define an AngularJS service

  • test_myService.js: where I define a Jasmine test for the service.

  • specRunner.html: a HTML file with the normal jasmine configuration and where I import the previous two other files and the Jasmine, Angularjs and angular-mocks.js.

This is the code for the service (that works as expected when I am not testing):

var myModule = angular.module('myModule', []);

myModule.factory('myService', function(){

    var serviceImplementation   = {};
    serviceImplementation.one   = 1;
    serviceImplementation.two   = 2;
    serviceImplementation.three = 3;

    return serviceImplementation

});

As I am trying to test the service in isolation, I should be able to access it and check their methods. My question is: how can I inject the service in my test without bootstrapping AngularJS?

For instance, how can I test the value returned for a method of the service with Jasmine like this:

describe('myService test', function(){
    describe('when I call myService.one', function(){
        it('returns 1', function(){
            myModule = angular.module('myModule');
                    //something is missing here..
            expect( myService.one ).toEqual(1);
        })

    })

});

Answer

Robert picture Robert · Oct 23, 2012

The problem is that the factory method, that instantiate the service, is not called in the example above (only creating the module doesn't instantiate the service).

In order to the service to be instantiated angular.injector has to be called with the module where our service is defined. Then, we can ask to the new injector object for the service and its only then when the service is finally instantiated.

Something like this works:

describe('myService test', function(){
    describe('when I call myService.one', function(){
        it('returns 1', function(){
            var $injector = angular.injector([ 'myModule' ]);
            var myService = $injector.get( 'myService' );
            expect( myService.one ).toEqual(1);
        })

    })

});

Another way would be passing the service to a function using 'invoke':

describe('myService test', function(){
    describe('when I call myService.one', function(){
        it('returns 1', function(){

            myTestFunction = function(aService){
                expect( aService.one ).toEqual(1);
            }

            //we only need the following line if the name of the 
            //parameter in myTestFunction is not 'myService' or if
            //the code is going to be minify.
            myTestFunction.$inject = [ 'myService' ];

            var myInjector = angular.injector([ 'myModule' ]);
            myInjector.invoke( myTestFunction );
        })

    })

});

And, finally, the 'proper' way to do it is using 'inject' and 'module' in a 'beforeEach' jasmine block. When doing it we have to realize that the 'inject' function it's not in the standard angularjs package, but in the ngMock module and that it only works with jasmine.

describe('myService test', function(){
    describe('when I call myService.one', function(){
        beforeEach(module('myModule'));
        it('returns 1', inject(function(myService){ //parameter name = service name

            expect( myService.one ).toEqual(1);

        }))

    })

});