Mocking the browser window object in Jest
# 25 Oct 2019 by SeanIn the application I work on most of the time, we pass a lot of data from Rails
to the React frontend by writing constants to the window
object in the
browser. Testing this can be a bit of a pain though.
While I’m sure there are better ways to do this, it’s certainly fast and a very straightforward solution to validate.
Testing JavaScript that relies on values in the window
object, though, is kind
of a pain in the ass.
The issue is that in our testing environment, the world begins and ends with
Jest and jsdom
, so there is no Rails server to write those values into the
window
.
The simple solution to this is to do a bit of a temp-var shuffle:
describe('a feature', () => {
let initialValue = window.AWESOME_VALUE
window.AWESOME_VALUE = 'new_value'
afterEach(() => window.AWESOME_VALUE = initialValue)
it('does some cool stuff', () => {
// ...
})
})
The issue is that while it’s pretty straightforward to mock functions and
modules in Jest, getting that constant mocked is hard. It also doesn’t work if
you try to do the shuffle and your module reads values off of the window into
local const
values at the top of a JS file, since those values are initialized
(and then never changed) when you import
or require
that file, which
happens before you have a chance to mock it.
The best solution I’ve come up with so far is to add a simple function that sits
between window
constants and the rest of your code:
// utils/window_values.js
export function windowValues() {
return window
}
And then, in testing, provide a mock with some easy ways to set and unset mocked values to support whatever functionality you need to exercise:
// utils/__mocks__/window_values.js
let mockedWindow = Object.create(null)
function addMockedWindowValue(key, value) {
mockedWindow[key] = value
}
function removeMockedWindowValue(key) {
mockedWindow[key] = undefined
}
function windowValues() {
return Object.assign({}, window, mockedWindow)
}
windowValues.__addMockedWindowValue = addMockedWindowValue
windowValues.__removeMockedWindowValue = removeMockedWindowValue
export { windowValues }
Then, you can easily organize your window
object as you need it in testing:
import { windowValues } from 'utils/window_values'
jest.mock('utils/window_values')
describe('windowValues', () => {
describe('when no values have been mocked', () => {
it('probably has no data', () => {
expect(windowValues().AWESOME_VALUE).toEqual(undefined)
})
})
describe('setting a mocked constant', () => {
it('returns the mock', () => {
// Set up a constant with a fixture
windowValues.__addMockedWindowValue('AWESOME_VALUE', { awesome: 'value' })
expect(windowValues().AWESOME_VALUE).toEqual({ awesome: 'value' })
})
})
describe('removing the mocked value', () => {
it('unsets the mock', () => {
// Set up a constant with a fixture
windowValues.__addMockedWindowValue('AWESOME_VALUE', { awesome: 'value' })
// ...then unset it
windowValues.__removeMockedWindowValue('AWESOME_VALUE')
expect(windowValues().AWESOME_VALUE).toEqual(undefined)
})
})
})