Mocking the browser window object in Jest

In 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)
    })
  })
})