14

I would like to unit test the following AngularJs service:

.factory('httpResponseInterceptor', ['$q', '$location', '$window', 'CONTEXT_PATH', function($q, $location, $window, contextPath){
     return {
         response : function (response) {
             //Will only be called for HTTP up to 300
             return response;
         },
         responseError: function (rejection) {
             if(rejection.status === 405 || rejection.status === 401) {
                 $window.location.href = contextPath + '/signin';
             }
             return $q.reject(rejection);
         }
     };
}]);

I have tried with the following suite:

describe('Controllers', function () {
    var $scope, ctrl;
    beforeEach(module('curriculumModule'));
    beforeEach(module('curriculumControllerModule'));
    beforeEach(module('curriculumServiceModule'));
    beforeEach(module(function($provide) {
       $provide.constant('CONTEXT_PATH', 'bignibou'); // override contextPath here
    }));
    describe('CreateCurriculumCtrl', function () {
        var mockBackend, location, _window;
        beforeEach(inject(function ($rootScope, $controller, $httpBackend, $location, $window) {
            mockBackend = $httpBackend;
            location = $location;
            _window = $window;
            $scope = $rootScope.$new();
            ctrl = $controller('CreateCurriculumCtrl', {
                $scope: $scope
            });
        }));

        it('should redirect to /signin if 401 or 405', function () {
            mockBackend.whenGET('bignibou/utils/findLanguagesByLanguageStartingWith.json?language=fran').respond([{"description":"Français","id":46,"version":0}]);
            mockBackend.whenPOST('bignibou/curriculum/new').respond(function(method, url, data, headers){
                return [401];
            });
            $scope.saveCurriculum();
            mockBackend.flush();
            expect(_window.location.href).toEqual("/bignibou/signin");
        });


    });
});

However, it fails with the following error message:

PhantomJS 1.9.2 (Linux) Controllers CreateCurriculumCtrl should redirect to /signin if 401 or 405 FAILED
    Expected 'http://localhost:9876/context.html' to equal '/bignibou/signin'.
PhantomJS 1.9.2 (Linux) ERROR
    Some of your tests did a full page reload!

I am not sure what is going wrong and why. Can anyone please help?

I just want to ensure the $window.location.href is equal to '/bignibou/signin'.

edit 1:

I managed to get it to work as follows (thanks to "dskh"):

 beforeEach(module('config', function($provide){
      $provide.value('$window', {location:{href:'dummy'}});
 }));

3 Answers 3

17

You can inject stub dependencies when you load in your module:

angular.mock.module('curriculumModule', function($provide){
            $provide.value('$window', {location:{href:'dummy'}});
        });
Sign up to request clarification or add additional context in comments.

4 Comments

Thanks. What I am supposed to use as the value for $window instead of {}?
whatever you want :). You probably just want to check that $window.location is not undefined so that you can check it later on when your test sets it to a value
I am using this method as well, but it breaks my tests that are referencing $location. How do you mock $window and get $location to reference it?
If you stub $location too, it will no longer reference $window.
2

To get this to work for me I had to make a minor adjustment. It would error out and say:

TypeError: 'undefined' is not an object (evaluating '$window.navigator.userAgent')

So I added the navigator.userAgent object to get it to work for me.

$provide.value('$window', {
  location:{
    href:'dummy'
  },
  navigator:{
    userAgent:{}
  }
});

Comments

2

I faced the same problem, and went a step further in my solution. I didn't just want a mock, I wanted to replace $window.location.href with a Jasmine spy for the better ability to track changes made to it. So, I learned from apsiller's example for spying on getters/setters and after creating my mock, I was able to spy on the property I wanted.

First, here's a suite that shows how I mocked $window, with a test to demonstrate that the spy works as expected:

describe("The Thing", function() {
    var $window;

    beforeEach(function() {
        module("app", function ($provide) {
            $provide.value("$window", {
                //this creates a copy that we can edit later
                location: angular.extend({}, window.location)
            });
        });

        inject(function (_$window_) {
            $window = _$window_;
        });
    });

    it("should track calls to $window.location.href", function() {
        var hrefSpy = spyOnProperty($window.location, 'href', 'set');

        console.log($window.location.href);
        $window.location.href = "https://www.google.com/";
        console.log($window.location.href);

        expect(hrefSpy).toHaveBeenCalled();
        expect(hrefSpy).toHaveBeenCalledWith("https://www.google.com/");
    });
});

As you can see above, the spy is generated by calling the below function: (it works for both get and set)

function spyOnProperty(obj, propertyName, accessType) {
    var desc = Object.getOwnPropertyDescriptor(obj, propertyName);

    if (desc.hasOwnProperty("value")) {
        //property is a value, not a getter/setter - convert it
        var value = desc.value;

        desc = {
            get: function() { return value; },
            set: function(input) { value = input; }
        }
    }

    var spy = jasmine.createSpy(propertyName, desc[accessType]).and.callThrough();

    desc[accessType] = spy;
    Object.defineProperty(obj, propertyName, desc);

    return spy;
}

Lastly, here's a fiddle demonstrating this in action. I've tested this against Angular 1.4, and Jasmine 2.3 and 2.4.

1 Comment

As of Jasmine 2.6 (maybe earlier, not sure) spyOnProperty has been merged into core and doesn't need to be implemented by the user: jasmine.github.io/api/edge/global.html#spyOnProperty

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.