32

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
  • 1,755
  • 2
  • 15
  • 18

3 Answers3

22

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 
});
KhaledMohamedP
  • 5,000
  • 3
  • 28
  • 26
  • 1
    I like this solution, as it's simple enough to usually get the job done. The other options are for more complex requirements. – Andrew Smith Mar 21 '17 at 13:33
  • 1
    Me too, I am super glad you found it useful – KhaledMohamedP Mar 21 '17 at 17:26
  • great simple solution. The only thing I had to do was make `href` a string instead of an object. – HussienK Aug 17 '17 at 14:44
  • 7
    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. – perry Dec 19 '17 at 03:52
  • 4
    @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`. – Daniel Elkington Sep 27 '18 at 00:42
  • 15
    M not sure how that worked for you guys since m getting error saying cannot assign to a read only property. – Arvin Nov 09 '18 at 11:38
  • Same as @arvin : "TypeError: Cannot assign to read only property 'window' of object '#'" – jmcollin92 Dec 30 '19 at 22:31
  • huh - worked for me - I was expecting the type-error the other folks in the comments got - maybe it's depenent on where you run your tests? I'm running from yarn command line. – Ryan S Jan 13 '23 at 19:02
14

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()

try-catch-finally
  • 7,436
  • 6
  • 46
  • 67
  • 1
    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? – Francesco Pezzella Apr 17 '16 at 16:55
  • 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 :). – try-catch-finally Apr 18 '16 at 06:19
  • 1
    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. – oligofren Jun 16 '17 at 12:00
14

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.

Danny Andrews
  • 456
  • 4
  • 10
  • This is poorly explained. Could someone please edit this and give a real explanation on what this means and how to do it? – mawburn Mar 08 '17 at 15:30
  • 1
    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 Jun 16 '17 at 11:59
  • 5
    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). – try-catch-finally Jul 06 '17 at 13:37
  • 1
    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. – Danny Andrews Aug 17 '17 at 15:51