Search code examples
angularjsunit-testingjasminespyon

Angularjs: mock location.path() with spyOn for a unit test


I have already read this post (and others) but I don't manage to make this simple unit test work. I'm using the version 2 of Jasmine. My factory is very simple:

angular.module('myApp')
    .factory('detectPath', function ($location, $rootScope) {
        'use strict';
        var locationPath = $location.path()
        function getPath () {
            if (locationPath === '/') {
                locationPath = 'home';
            } else {
                locationPath = '';
            }
            $rootScope.path = locationPath;
        }
        getPath();
        return locationPath;
    });

And my unit test is just as simple:

'use strict';
describe('Factory: detectPath', function () {
    var detectPath, $rootScope, $location;

    beforeEach(module('myApp'));
    beforeEach(inject(function (_detectPath_, _$rootScope_, _$location_) {
        detectPath = _detectPath_;
        $rootScope = _$rootScope_;
        $location = _$location_;
        spyOn($location, 'path').and.returnValue('/');
    }));

    it('should return pathName', function ($location) {
        expect($rootScope.path).toBe('home');
    });
});

This doesn't pass the test (I get the error expect false to be "home").

What I am doing wrong? Is there a way to verify that spyOn has been called (only once)?


Solution

  • There are two main problems with your code.

    First of all, your getPath() function is executed before you are setting spy. You should either set the spy in the previous beforeEach or inject your factory in the test (I went for the second solution).

    The second problem (which does not influence the test yet) is that you hide your $location variable with test's function argument - you will not be able to access it as it will always be undefined. After I removed this arg, I'm able to test if spy has been called with expect(...).toHaveBeenCalled().

    Here is a working code:

    describe('Factory: detectPath', function () {
        var detectPath, $rootScope, $location;
    
        beforeEach(module('myApp'));
        beforeEach(inject(function (_$rootScope_, _$location_) {
            $rootScope = _$rootScope_;
            $location = _$location_;
            spyOn($location, 'path').and.returnValue('/');
        }));
    
        it('should return pathName', function () {
            inject(function (detectPath) {
                expect($location.path).toHaveBeenCalled();
                expect($rootScope.path).toBe('home');
            });
        });
    });
    

    And JSFiddle (using Jasmine 1.3 but the only difference in this sample is that you call and.returnValue in Jasmine 2 and returnValue in Jasmine 1.3).