Jest mock window object before importing a dependency

Viewed 1667

I need to have a value set in the window object before a dependency is imported. Say I have this code

// foo.test.js
import { dependency } from './foo'

describe('...', () => {
  it('...', () => {
    // use dependency
  })
})

But for dependency to be imported I need to have a value defined in window.myValues

// foo.js
export const dependency = {
  key: window.myValue.nestedValue
}

That code will give me an error when importing the file because window.myValue.nestedValue is trying to access the property nestedValue of undefined.

How can I get that done?

Edit Following christianeide's answer below I get the following error

  ● Test suite failed to run

    TypeError: Cannot convert undefined or null to object

      2 |   delete global.window.myValue
      3 |   global.window = Object.create(window)
    > 4 |   global.window.myValue = {
        |                 ^
      5 |     nestedValue: 'someValue'
      6 |   }
      7 | }

      at module.exports (jest.setup.js:4:17)
      at node_modules/@jest/core/build/runGlobalHook.js:82:17
      at ScriptTransformer.requireAndTranspileModule (node_modules/@jest/transform/build/ScriptTransformer.js:684:24)
      at node_modules/@jest/core/build/runGlobalHook.js:72:27
      at pEachSeries (node_modules/p-each-series/index.js:8:9)
      at async _default (node_modules/@jest/core/build/runGlobalHook.js:58:5)
      at async runJest (node_modules/@jest/core/build/runJest.js:345:5)
4 Answers

es6 imports are "hoisted", meaning wherever you write them in the code, they'll get processed before the importing module is executed, so the imported module is always executed before the importing module. In your case, this means foo.js executes before foo.test.js, so even if you properly mock your property of window in your tests, foo.js will not see your mock.

You can get around this by using require in your tests to import foo.js after the property of window has been mocked.

// foo.test.js
window.myValue = { nestedValue: MOCK_NESTED_VALUE };

const { dependency } = require('./foo');

describe('...', () => {
  it('...', () => {
    // use dependency
  })
})

As other answers have pointed out, if myValue is one of existing "sytem" properties of window such as window.location, you might have to delete it first. When deleting, don't forget to back it up so that you can restore it when cleaning up after your test.

Try assigning values to global.window.

Something like this:

delete global.window.myValue;
global.window = Object.create(window);
global.window.myValue = {
    nestedValue: 'someValue',
};

This can be done inside jest.setup.js, but you could also probably define the values inside foo.test.js

I was able to append properties on the window object by creating a setup.js:

global.propertyA = () => {};
global.nestedPropertyB = {
  propertyC: () => {}
};

And setting the file in setupFilesAfterEnv in my jest.config.js file:

module.exports = {
  setupFilesAfterEnv: ['<rootDir>/tests/js/setup.js']
}

I have solved such an issue with jest.mock: as it's hoisted above import statements, it's possible to execute any code before and after importing an actual module.

In my case I needed to test some functionality and set node env to a value, different from 'test', and, as I needed this substitution only for the time of import, I set its value back to 'test' before returning the actual module. Of course, it may vary depending on use cases - it's just an example of executing code before and after importing is done. Also i tried to use a getter for a particular module field, and it also worked.

import ApiBase from "../ApiBase";

jest.mock('../ApiBase', () => {
    process.env.NODE_ENV = '';
    const actual = jest.requireActual('../ApiBase');
    process.env.NODE_ENV = 'test';
    return actual;
});

Related