jest: Unable to change window.location using Object.defineProperty
Do you want to request a feature or report a bug? Report a bug
What is the current behavior?
Calling the following from inside a test suite:
Object.defineProperty(location, "hostname", {
value: "example.com",
writable: true
});
throws the following error:
TypeError: Cannot redefine property: hostname
at Function.defineProperty (<anonymous>)
What is the expected behavior?
The code should not throw an exception, and window.location.hostname === "example.com"
should evaluate true.
From the looks of it, jsdom
now sets window.location to be unforgeable. The only way to change the values within window.location
is to use reconfigure
, but (per #2460) Jest doesn’t expose jsdom
for tests to play around with.
Please provide your exact Jest configuration and mention your Jest, node, yarn/npm version and operating system.
Jest version: 22.0.1 Node version: 8.6.0 Yarn version: 1.2.0 OS: macOS High Sierra 10.13.2
About this issue
- Original URL
- State: closed
- Created 7 years ago
- Reactions: 121
- Comments: 84 (13 by maintainers)
Commits related to this issue
- chore(devDeps): rollback to Jest v21 https://github.com/facebook/jest/issues/5124 ``` Object.defineProperty(location, "hostname", { value: "example.com", writable: true }); ``` ``` TypeError: C... — committed to Availity/sdk-js by deleted user 7 years ago
- [App] LandingPage : Game the testing for this is, like with isRootUrlPath(), half-assed. It looks like changing the url in Jest is a bit of a pain (ref: https://github.com/facebook/jest/issues/5124),... — committed to swpease/codenames by swpease 6 years ago
- Use jest-environment-jsdom-global to reconfigure jsdom per https://github.com/facebook/jest/issues/5124#issuecomment-352838312 — committed to department-of-veterans-affairs/vets-website by cehsu 6 years ago
- deps/jest: Add `jsdom-global` to dev dependencies For a later test, we'll want to be able to set up the test environment to appear as though it's a browser at a specified URL. Jest currently uses `js... — committed to rk-for-zulip/zulip-mobile by rk-for-zulip 5 years ago
- Use jest-environment-jsdom-global to reconfigure jsdom per https://github.com/facebook/jest/issues/5124#issuecomment-352838312 — committed to bkjohnson/vets-website-content-build by cehsu 6 years ago
- frontend/../Banner.test.js: change replace window.history.pushState(...) with `window.location` method used in List.test.js [+] Reasons: - Consistency with List.test.js - Altering `window` with cust... — committed to NYULibraries/ariadne by da70 a year ago
Since
location
cannot be overridden directly on the jsdomwindow
object, one possible approach is to override it on a derived object:Here’s a simple solution that works.
I’ve published a new package on npm called
jest-environment-jsdom-global
, which may help with the problems some people are having withObject.defineProperty
.I use this to solve the issue:
Inspired by @RubenVerborgh & annemarie35
A solution similar to @sahalsaad :
Thanks @modestfake - sorry for the dumb mistake!
Ok, I see it now -
this.global
on a Jest environment object gets set asglobal
in a Jest test file. That makes sense - thanks for helping me through it! If there’s enough interest, I could package the repaired version of that repo and put it onnpm
asjest-environment-jsdom-global
.However, I do hope there’s a cleaner way to do this in Jest in the future. This isn’t a low friction way to change
window.location
-Could there be a new docblock, like there is for
@jest-environment
? For example…Or, maybe JSDom can be exposed on a special part of the
jest
object - something like:(which would have the added benefit of being able to change
window.top
)This has been working for me, using jest 26.5
@sahalsaad thanks! I used a variation of your solution to mock window.location.search:
So, without using jsdom directly, here is solution I came up for my stuff:
Works for Jest 21.2.1 (I tested on that one):
Go into your Jest settings (for example I’ll use package.json):
"jest": { "testURL": "http://localhost" }
Now you will be able to change window.location object and then you can set URL to whatever you like during tests.
Hopefully this helps someone.
Maybe it’s help anyone.
You just need write in setupTests.js
I used
along with
testURL
in jest configprobably a better solution:
Going to close this as there’s nothing to do on Jest’s side, and workarounds exist (feel free to continue the discussion!)
It’s very unfortunate that someone has to jump though this many hoops just to change window.location.href. I just started using Jest and I am about to reconsider my choice of testing framework given this issue. Is there really no better solution than the ugly hacks suggested above?
I solved this doing:
So here are all the options that I would consider preferable based on the number of lines required to change window.location; none of these currently work:
Setting href directly doesn’t work because jsdom throws an exception with message “Error: Not implemented: navigation (except hash changes)”:
Using Object.defineProperty doesn’t work because JSDOM has made the window’s location property [Unforgeable]:
Creating an instance of jsdom and configuring it doesn’t work because jest seems to use its own instance which is not exposed for easy access:
Making options 1 or 2 work would require jsdom to backtrack on their current goals of behaving more like a real browser. Therefore, it seems like the only option we have is to make it easier to reconfigure the instance of jsdom that jest uses. Would it make sense to expose that instance directly on the global jest object; i.e. permitting something like this:
Most of the workarounds in here haven’t been working for me. In particular, the
jest-environment-jsdom-global
doesn’t seem to work because installingjest-environment-jsdom
causessetImmediate
to no longer be defined in the global scope and we rely on it fairly heavily for use in the flushPromises solution. I had some issues with some of the other solutions causing security exceptions withhistory
when trying to change the origin.In the end, I decided that we didn’t need to change the origin for our purposes and in that case, we might as well just use the built in browser mechanism for changing the URL using
history.replaceState
. If your application doesn’t rely on history listeners, this might be perfectly acceptable for you and doesn’t rely on any other libraries or hacks. I wish jest would just implement a proper mechanism for changing the URL.try:
@SimenB in cases where code is trying to set
location.href
, yes,location.assign()
is better. But if you’re testing behaviour that readslocation.href
,location.assign()
doesn’t solve the problem, especially sincelocation.assign()
in JSDOM doesn’t actually do anything.The idea behind using
reconfigure
is to activate a code path that’s only enabled whenlocation.href
is formed in a particular way. In our case, we had some code that changed depending on the current domain - the code is smelly, yes, but it’s also necessary, and the best way to mitigate smelly code is to have testing fixtures that capture the behaviour and ensure that it stays in place.@SimenB the stated workaround (“use
jest-environment-jsdom-global
”) feels like an extremely suboptimal solution to what’s obviously a very common problem. Anybody upgrading to Jest 22 now needs to add a dependency to that third party package and (from a user’s perspective) re-write some of their tests. This is a regression in Jest’s behavior.Is there a solution to this that we could build into the default
jest-environment-jsdom
? Happy to make a PR with your guidance.your answer is the only one that works for my situation, thanks!
Solved my issue using the solution given by @kdelmonte, I’ve had to mock the
window.location.search
variable. so I’ve usedAdding an example of testing
location.search
based on @vastus’s solution:I’d recommend switching to
window.location.assign
, that way you can mock the function.This is no longer workin 😢
I have the similar issue. You can create your own JSDOMEnvironment and expose jsdom object to the global like this.
And then you can call jsdom.reconfigure in your test case as you like
Is there a way to use this with Enzyme and a mounted component to test for redirects?
The below test passed before upgrading Jest:
After upgrading Jest and implementing jest-environment-jsdom-global, I tried the following to no avail:
(window.location.href still equals ‘https://mysuperawesomesite.com/’, didn’t get changed to (‘https://mysuperawesomesite.com/new’).
The click event on the element does not redirect when using this method, and the redirect occurs by setting window.location.href.
Unclear on how to properly test this or if the tests that had previously used Object.defineProperty were poorly constructed to begin with. Thanks in advance for any assistance.
EDIT: SOLVED
Was able to solve this by using window.location.assign(url) instead of window.location.href = href. This allowed me to stub out the assign method and test whether it was being properly called. See below:
@oliverzy Like this?
That throws
jsdom is not defined
, but I may be misinterpreting.I’m using the following mock as a utility for working with
Location
Then in my tests
Just for completeness, since the solution is stranded in the middle of this thread…
@petar-prog91’s solution will work on Jest 21, but not Jest 22 with the updated
jsdom
.In order to run on the latest Jest, use something like
jest-environment-jsdom-global
(full disclosure, this is my package) to expose thejsdom
object and usejsdom.reconfigure
, which will have the same (or, at least, a similar) effect.Had the same issue, but this piece of code worked with me
I found myself stubbing tricky objects like these all the time, and created a flexible helper function:
And then usage:
And you can stub whatever you want:
pathname
,href
, etc… This gets you the added free benefit of cleanup.The key is you can’t mess with
location
itself, so just swap outlocation
with a fake, and then put it back when the test is done.There’s nothing we can do on jest’s side. I’m sure jsdom would love a PR supporting it. https://github.com/jsdom/jsdom/issues/2112
@a-n-d-r-3-w any particular reason why you have jsdom in your package.json? It comes bundled with Jest.
you need to write
jsdom
rather thanglobal.jsdom
in your tests.@SimenB There is a big difference between
.assign
and.href
You can read on MDN. First one has a major cross-domain restriction. In my code I want to redirect parent page from an iframe where my code is running. Those are cross-domains. And the only way I can do this is by changinghref
. I would love if this issue be re-opened, since I don’t see a current workaround and I will have to just not to have test for this function. Which obviously sucks.I’ve fixed this issue by re-implementing window.location & window.history: https://gist.github.com/tkrotoff/52f4a29e919445d6e97f9a9e44ada449
It’s heavily inspired by https://github.com/jestjs/jest/issues/5124#issuecomment-792768806 (thx @sanek306) and firefox-devtools window-navigation.js (thx @gregtatum & @julienw)
It comes with unit tests and it works well with our source base using Vitest.
You can then use window.location in your tests like you would expect:
As I can see in my debugging session,
global.location
is implemented via getter but not a simple property. Wouldn’t it be safer to redefine it like this?Though it’s hard to imagine why would I want to use the original
global.location
, it seems just a bit more correct. And this code works fine for me, of course. I just accesslocation.pathname
, but this object may be easily extended with somejest.fn()
if needed.@simon360 Yes, based on my understanding of documentation, it takes about
which overrides url globally and not per test. Please help!
Using
window.history.pushState
andtestUrl
worked for mehttps://github.com/facebook/jest/issues/5124#issuecomment-359411593
@andyearnshaw the
jsdom.reconfigure
method has awindowTop
member in its object.If you’re using the JSDOM environment (
jest-environment-jsdom-global
) I linked above, you could do:in your tests to mock out a top value.
@danielbayerlein what the use case to make it writable but not override it actually? Maybe understanding this can help me to come up with workaround
@modestfake we have upgraded from JSDOM@9 to JSDOM@11, my guess is that they changed how the variable is defined
This worked exactly well for the problem I was having
Not working for me either
@ydogandjiev feel free to contribute to the project to solve this issue. Remember that this is open source, so rampaging in with comments like “unacceptable” and “ridiculous” do nothing to help anyone.
@abhijeetNmishra I’m not sure that this issue is the best place to discuss. Would you mind opening an issue on the
jest-environment-jsdom-global
repository where we can work through it? Thanks!@UserNT I noted version on which it works here and I use it on production testing suits extensively. If it doesnt work on a newer versions, I’m sorry, come up with your own solution instead of just random bashing.
Stop posting this, it does not work on jest": “^22.4.2”
@andrewBalekha What about this?