Stubbing window.location.href with Sinon
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:
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.
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.
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.
Francesco Pezzella
Updated on December 28, 2020Comments
-
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 about 8 yearsThank 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 forwindow.location.href
in a way that the tested code can access that value, without having the browser to follow the URL? -
try-catch-finally about 8 yearsUnfortunately 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 about 7 yearsThis is poorly explained. Could someone please edit this and give a real explanation on what this means and how to do it?
-
Andrew Smith about 7 yearsI like this solution, as it's simple enough to usually get the job done. The other options are for more complex requirements.
-
KhaledMohamedP about 7 yearsMe too, I am super glad you found it useful 😀
-
oligofren almost 7 yearsIMHO, this was explained perfectly adequate wrt the discussion. To elaborate his response, change the client code to replace
window.location = url
withwindow.location.assign(url)
. You can then stub theassign
method of thelocation
object like this:var stub = sinon.stub(window.location, 'assign')
-
oligofren almost 7 yearsAnother 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 almost 7 yearsThis answer is wrong. You cannot reassign (stub)
location.assign
, at least in Chrome awindow.location.assign = function() { console.log("meh"); }
has no effect. When callinglocation.assign()
I getUncaught 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 over 6 yearsgreat simple solution. The only thing I had to do was make
href
a string instead of an object. -
Danny Andrews over 6 yearsThanks 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 over 6 yearsCorrect 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 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 itglobal.window = originalWindow
. -
Arvin over 5 yearsM not sure how that worked for you guys since m getting error saying cannot assign to a read only property.
-
jmcollin92 over 4 yearsSame as @arvin : "TypeError: Cannot assign to read only property 'window' of object '#<Window>'"