24

I have a test that imports a component that in turn imports a helper file that uses the window object to pull out a query string parameter. I get the following error about window:

FAIL src/js/components/__tests__/Controls.test.jsx ● Test suite failed to run ReferenceError: window is not defined 

Controls.jsx:

import { Unwrapped as Controls } from '../Controls' describe('<MyInterestsControls />', () => { it('should render the component with the fixture data', () => { const component = shallow( <UnwrappedMyInterestControls dashboardData={dashboardData} loadingFlags={{ controls: false }} /> ) expect(component).toMatchSnapshot() }) }) 

Controls.jsx imports ./helpers/services.js which contains the following:

import * as queryString from 'query-string' const flag = queryString.parse(window.location.search).flag || 'off' ^^^^^^ this seems to be the problem 

I have attempted to import jsdom:

import { JSDOM } from 'jsdom' 

And implemented the solution presented here at the top of my test file:

const { JSDOM } = require('jsdom'); const jsdom = new JSDOM('<!doctype html><html><body></body></html>'); const { window } = jsdom; function copyProps(src, target) { const props = Object.getOwnPropertyNames(src) .filter(prop => typeof target[prop] === 'undefined') .map(prop => Object.getOwnPropertyDescriptor(src, prop)); Object.defineProperties(target, props); } global.window = window; global.document = window.document; global.navigator = { userAgent: 'node.js', }; copyProps(window, global); 

However, I still get the error and it seems JSDOM's window object isn't exposed to the test.

How can I properly expose global objects like window or document to a Jest test?

Relevant package.json
 "scripts": { "test:watch": "NODE_ENV=test jest --watch" }, ... "devDependencies": { ... "jest": "^20.0.4", "jest-mock": "^21.2.0", "jsdom": "^11.0.0", ... }, ... "jest": { "verbose": true, "collectCoverageFrom": [ "src/js/helpers/preparePayload.js", "src/js/components-ni", "!**/node_modules/**", "!**/dist/**" ], "coverageThreshold": { "global": { "statements": 50, "branches": 50, "functions": 50, "lines": 75 } }, "testEnvironment": "jest-environment-node" } 
6
  • ^^ Since I really messed up the markdown and the link in the bounty description, this is the link to the enzyme docs Commented Oct 16, 2017 at 22:33
  • which version of jest are you using? Commented Oct 18, 2017 at 1:35
  • @JoseAPL jest@^20.0.4, [email protected] Commented Oct 18, 2017 at 1:37
  • could you please add your package.json, by default jest uses the testEnvironment to browser-like jsdom so you don't have to import jsdom to your unit tests Commented Oct 18, 2017 at 1:54
  • @JoseAPL I've added the relevant portions from the package.json Commented Oct 18, 2017 at 2:16

7 Answers 7

25
+50

As mentioned by @RiZKiT in the comment below, since Jest v27.0 the default test environment has changed from "jsdom" to "node".

Your problem relies on the configuration.

In the moment you set:

"testEnvironment": "jest-environment-node" 

You are changing the default configuration from Jest which is browser-like to jest-environment-node (Node.js-like) meaning that your test will be run under a Node.js environment

To solve it either you set your testEnvironment to jsdom Or you remove the testEnvironment from your configuration, so it will take the default value in yourpackage.json:

 ... "jest": { "verbose": true, "collectCoverageFrom": [ "src/js/helpers/preparePayload.js", "src/js/components-ni", "!**/node_modules/**", "!**/dist/**" ], "coverageThreshold": { "global": { "statements": 50, "branches": 50, "functions": 50, "lines": 75 } } } 

This is what they say in the documentation

testEnvironment [string] # Default: "jsdom"

The test environment that will be used for testing. The default environment in Jest is a browser-like environment through jsdom. If you are building a node service, you can use the node option to use a node-like environment instead.


## Do you need the `node` environment?

As I could see, your tests are meant to be run under a browser-like environment.

If you ever need an explicit Node.js environment, better you isolate that case using @jest-environment:

/** * @jest-environment node */ test('use node in this test file', () => { expect(true).not.toBeNull(); }); 

Or the other way around, if you are meant to run the tests under a Node.js environment:

/** * @jest-environment jsdom */ test('use jsdom in this test file', () => { const element = document.createElement('div'); expect(element).not.toBeNull(); }); 

## Conclusion

With this you can avoid importing jsdom manually and setting global variables. jsdom will mock the DOM implementation automatically.

If you need to change the environment for your tests, use the notation @jest-environment.

Sign up to request clarification or add additional context in comments.

1 Comment

Since Jest 27 "jsdom" isn't the default anymore but "node", see github.com/facebook/jest/pull/9874 and here jestjs.io/blog/2021/05/25/jest-27#flipping-defaults.So you have to set "jsdom" explicitly now.
4

You could try doing

global.window = new jsdom.JSDOM().window; global.document = window.document; 

1 Comment

Why would that help? Can you elaborate in your answer? (But without "Edit:", "Update:", or similar - the question/answer should appear as if it was written today.)
4

See Expose jsdom to global environment #2460

It seems like one of the contributors declared that he is not planning to expose jsdom to global under the Jest environment.

However, you could use Object.defineProperty(window, 'location', {value: '…'} API to approach it, like the developer from Facebook do. In your case it could be like:

 Object.defineProperty(window, 'location', { value: { search: ... }, }) 

2 Comments

Can you provide an example showing how to use this in a test. As is, I still get the reference error.
This solved my issue, just need to add it in beforeEach() of the test
4

Here you can find examples of how to do this:

DOM Testing React Applications with Jest

For example:

import {jsdom} from 'jsdom'; const documentHTML = '<!doctype html><html><body><div id="root"></div></body></html>'; global.document = jsdom(documentHTML); global.window = document.parentWindow; 

1 Comment

import { jsdom } from 'jsdom'; const documentHTML = '<!doctype html><html><body><div id="root"></div></body></html>'; global.document = jsdom(documentHTML); global.window = document.parentWindow; import { Unwrapped as UnwrappedMyComponent } from '../MyComponent'; (MyComponent uses window) => ReferenceError: window is not defined
2

You can simply mock location:

global.location = {search: 'someSearchString'} 

Also note, that global in your test is the global context for the file to test (global === window)

Note this will only work if your module make the window.location call after the test has been finishing import all the modules.

export default () => window.location 

So if your module looks like this:

const l = window.location export default l 

it will not work. In this case you could mock the module using jest.mock.

6 Comments

When I add this object to the top of my test: global.window = { location: { search: { api: 'fixture' } } } I still receive ReferenceError: window is not defined when I import { Unwrapped as UnwrappedMyComponent } from '../MyComponent' which contains itself contains an import of another script which uses window.location.search. I've tried it at the top of the file and also below my enzyme named imports. Have I done something incorrectly?
No, if the imported modules use window in its initialization code then there is no way to set this. In this case I would mock out this dependency.
How do you mean? Can you give an example?
Updated my answer
Note this will only work if your module make the window.location call after the test has been finishing import all the modules. > I'm not sure I understand what you mean by this or how I can control it. In this case you could mock the module using jest.mock Are you talking about this package?
|
1

I am not sure, but I think you could do it with jest.fn():

global.window = jest.fn(() => { location: { ... } }) 

Maybe even as window = jest.fn(...).

Comments

0

In my case, I was injecting a custom environment file's value into a Vue.js component using Ruby on Rails. So, I was getting a base URL for assets as undefined in a Jest snapshot. The solution was to include a setup file in jest.config.jssetupFiles as an array like below.

jest.config.js

{ setupFiles: [ "./app/javascript/tests/testsConfig.js", ], } 

testsConfig.js

process.env.BASE_DOMAIN = process.env.BASE_DOMAIN || 'http://localhost:3000/' window.TestsConfig = { "CLOUD_FRONT_BASE_URL": "https://cdn.site.com" } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.