Stubbing window.location.href with Sinon

35,590

Solution 1

Stubs cannot replace attributes, only functions.

The error thrown reinforces this:

TypeError: Custom stub should be a function or a property descriptor

From the documentation:

When to use stubs?

Use a stub when you want to:

  1. Control a method’s behavior from a test to force the code down a specific path. Examples include forcing a method to throw an error in order to test error handling.

  2. When you want to prevent a specific method from being called directly (possibly because it triggers undesired behavior, such as a XMLHttpRequest or similar).

http://sinonjs.org/releases/v2.0.0/stubs/


Possible solution

While many builtin objects can be replaced (for testing) some can't. For those attributes you could create facade objects which you then have to use in your code and being able to replace them in tests.

For example:

var loc = {

    setLocationHref: function(newHref) {
        window.location.href = newHref;
    },

    getLocationHref: function() {
        return window.location.href;
    }

};

Usage:

loc.setLocationHref('http://acme.com');

You can then in your test write

var stub = sinon.stub(loc, 'setLocationHref').returns('http://www.foo.com');

Note the chained returns() call. There was another error in your code: the third argument has to be a function, not value on another type. It's a callback, not what the attribute should return.

See the source code of stub()

Solution 2

You need to use global to mock the window object for your test in beforeEach or it

e.g.

it('should compose a Log', () => {
   global.window = {
       location: {
           href: {
               value: 'foo'
           }
       }
   }
  //.... call the funciton 
});

Solution 3

Use window.location.assign(url) instead of overwriting the value of window.location. Then you can just stub the assign method on the window.location object.

http://www.w3schools.com/jsref/met_loc_assign.asp


UPDATE: I tested this in a headless browser, but it may not work if you run your tests in Chrome. See @try-catch-finally's response.

Share:
35,590
Francesco Pezzella
Author by

Francesco Pezzella

Updated on December 28, 2020

Comments

  • Francesco Pezzella
    Francesco Pezzella over 3 years

    I am trying to test some client-side code and for that I need to stub the value of window.location.href property using Mocha/Sinon.

    What I have tried so far (using this example):

    describe('Logger', () => {
        it('should compose a Log', () => {
            var stub = sinon.stub(window.location, 'href', 'http://www.foo.com');
        });
    });
    

    The runner displays the following error:

    TypeError: Custom stub should be a function or a property descriptor

    Changing the test code to:

    describe('Logger', () => {
        it('should compose a Log', () => {
            var stub = sinon.stub(window.location, 'href', {
                value: 'foo'
            });
        });
    });
    

    Which yields this error:

    TypeError: Attempted to wrap string property href as function

    Passing a function as third argument to sinon.stub doesn't work either.

    Is there a way to provide a fake window.location.href string, also avoiding redirection (since I'm testing in the browser)?

  • Francesco Pezzella
    Francesco Pezzella about 8 years
    Thank you @try-catch-finally Your answer is indeed correct and I marked it as such, but unfortunately I need to test some code whose behaviour rely directly on window.location.href value. Since I am using the Mocha html runner to test in the browser, is there any way to craft a value for window.location.href in a way that the tested code can access that value, without having the browser to follow the URL?
  • try-catch-finally
    try-catch-finally about 8 years
    Unfortunately no. But another hack that come to my mind is: to configure the underlining web server to return the test suite runner at any path and make the test framework to constandly save the results into the local storage and reload it upon page load (redirect). Of course you should then not redirect to "foo.com" but "/foo/dir". Please search Stackoverflow for similar questions on this topic and if you can't find answers ask another question (that's as clearly written as this one :).
  • mawburn
    mawburn about 7 years
    This is poorly explained. Could someone please edit this and give a real explanation on what this means and how to do it?
  • Andrew Smith
    Andrew Smith about 7 years
    I like this solution, as it's simple enough to usually get the job done. The other options are for more complex requirements.
  • KhaledMohamedP
    KhaledMohamedP about 7 years
    Me too, I am super glad you found it useful 😀
  • oligofren
    oligofren almost 7 years
    IMHO, this was explained perfectly adequate wrt the discussion. To elaborate his response, change the client code to replace window.location = url with window.location.assign(url). You can then stub the assign method of the location object like this: var stub = sinon.stub(window.location, 'assign')
  • oligofren
    oligofren almost 7 years
    Another alternative we often give people on the Sinon issue tracker is to a ready-made facade layer for globals such as wrapple in your client code, and then stub out its methods. Achieves the same thing.
  • try-catch-finally
    try-catch-finally almost 7 years
    This answer is wrong. You cannot reassign (stub) location.assign, at least in Chrome a window.location.assign = function() { console.log("meh"); } has no effect. When calling location.assign() I get Uncaught TypeError: Failed to execute 'assign' on 'Location': 1 argument required, but only 0 present. (which indicates the native method complains about the missing argument).
  • HussienK
    HussienK over 6 years
    great simple solution. The only thing I had to do was make href a string instead of an object.
  • Danny Andrews
    Danny Andrews over 6 years
    Thanks for the correction. I only tried this in a headless browser and it worked for me. But if you are running your mocha tests in a browser such as Chrome, than this method would not work.
  • perry
    perry over 6 years
    Correct me if I'm wrong but overriding the global object would follow through to the rest of your test files right? Meaning it would be overriden for any other test cases. Might not be a a problem but a FYI.
  • Daniel Elkington
    Daniel Elkington over 5 years
    @perry To solve this problem at the start of the test I saved the global object const originalWindow = global.window and then at the end of the test I restored it global.window = originalWindow.
  • Arvin
    Arvin over 5 years
    M not sure how that worked for you guys since m getting error saying cannot assign to a read only property.
  • jmcollin92
    jmcollin92 over 4 years
    Same as @arvin : "TypeError: Cannot assign to read only property 'window' of object '#<Window>'"