8

I have a javascript utility file that makes API requests. It has two conditional's at the top that rely on window.VarA and window.VarB. Where VarA and VarB are variables given to the window by our software. If they are set the code inside the conditional is run. Since this file doesn't actually render a window I can't set these properties to anything and get code coverage inside the conditional.

I've attempted rendering a default window but that doesn't seem to work. I have Jest and Enzyme available to me.

The code from the object that I am testing looks like this

let param = "";
let url = "";
if(window.VarA !== underfined){
     param = window.VarA; <<--need coverage here
}
if(window.VarB !== underfined){
     url = window.VarB; <<--need coverage here
}

Is there some way to mock these window properties for my tests?

jwolsborn
  • 576
  • 1
  • 6
  • 26

2 Answers2

9

The default test environment for Jest is a browser-like environment using jsdom which provides a window object.

If the test environment is jsdom then the window object is accessible as either window or global.

Setting variables on window then becomes as easy as doing the following:

window.VarA = 'foo';  // global.VarA works as well
window.VarB = 'bar';  // global.VarB works as well

The trickier part is getting those variables set before your code runs.

Consider the following config.js module:

let param = "";
let url = "";
if (window.VarA !== undefined) {
  param = window.VarA;
}
if (window.VarB !== undefined) {
  url = window.VarB;
}

export const config = {
  param,
  url
}

If you use require then the code doesn't run until the module is required, allowing for a test like this:

test('setting window variables', () => {
  window.VarA = 'foo';  // global.VarA works as well
  window.VarB = 'bar';  // global.VarB works as well

  const { config } = require('./config');  // now require the module

  expect(config.param).toBe('foo');  // SUCCESS
  expect(config.url).toBe('bar');  // SUCCESS
});

On the other hand, if you use import then the variables need to be defined on window before the test runs since imports are hoisted and happen before any test code runs:

import { config } from './config';  // this runs first

test('setting window variables', () => {
  window.VarA = 'foo';  // <= this doesn't affect config
  window.VarB = 'bar';  // <= this doesn't affect config

  expect(config.param).toBe('foo');  // FAIL
  expect(config.url).toBe('bar');  // FAIL
});

So if you are using import then you would need to use something like setupFilesAfterEnv to set the window variables before your test begins running:

// Configure Jest using setupFilesAfterEnv
// to run a module that sets the window variables
// so they are already set before the test begins...

import { config } from './config';

test('setting window variables', () => {
  expect(config.param).toBe('foo');  // SUCCESS
  expect(config.url).toBe('bar');  // SUCCESS
});
Brian Adams
  • 43,011
  • 9
  • 113
  • 111
  • I haven't tried applying this yet but thanks for the response on another one of my questions! You're a rockstar man! – jwolsborn Mar 04 '19 at 16:44
  • Many Thanks!!! I had a loosely similar proble, I could'nt access window.myVar defined on my index.js file, from a Jest Test made on the parallel index.ejs file. Doing: 'import index from '../../src/index.js' fixed my day and made window.myVar accessible on the test. Thanks again. – Josh Aug 16 '20 at 18:16
  • 1
    I added global.myVar="myValue" in setupTests.js file. This add myVar property to window object automatically/. It worked!! – Mounika Bathina Feb 02 '22 at 13:50
1

Great answer by Brian Adams. Just as an alternative to setupFilesAfterEnv you could also dynamically import module in the body of a test:

test('setting window variables', async () => {
  window.VarA = 'foo';
  window.VarB = 'bar';
  const { config } = await import('./config');
  expect(config.param).toBe('foo');  // SUCCESS
  expect(config.url).toBe('bar');  // SUCCESS
});

Please note that window variable cannot be reassigned - further tests will still reference originally assigned value in the first place or produce side effects. For example:

test('setting window variables', async () => { ... });

test('setting a different window variable A', async () => {
  window.VarA = 'baz';
  const { config } = await import('./config');
  expect(config.param).toBe('baz');  // FAIL, it's still 'foo'
});

So to avoid mental confusion it's worth to move window assignments in beforeAll or beforeEach:

beforeAll(() => {
  window.VarA = 'foo';
  window.VarB = 'bar';
});

test('setting window variables', async () => {
  const { config } = await import('./config');
  expect(config.param).toBe('foo');
  expect(config.url).toBe('bar');   
});
Anton Koning
  • 376
  • 4
  • 6