Jest: Bug mockin global variables

Created on 29 May 2017  ·  5Comments  ·  Source: facebook/jest

// lib.js
/**
 * @module lib
 */
(function(global) {
    var globalScope = Object.create || 'globalScope';

    /**
     * @returns {{localScope: (*|string), globalScope: (*|string)}}
     */
    function lib() {
        var localScope = Object.create || 'localScope';
        return {
            localScope : localScope,
            globalScope : globalScope
        };
    }

    var defineAsGlobal = true;
    if(typeof exports === 'object') {
        module.exports = lib;
        defineAsGlobal = false;
    }

    if(typeof modules === 'object' && typeof modules.define === 'function') {
        modules.define('lib', function(provide) {
            provide(lib);
        });
        defineAsGlobal = false;
    }

    if(typeof define === 'function') {
        define(function(require, exports, module) {
            module.exports = lib;
        });
        defineAsGlobal = false;
    }

    defineAsGlobal && (global.lib = lib);
})(this);

```JavaScript
// test.js
const lib = require('./lib');

describe('Test mocking global variable', function () {

test('Native global Object', function() {
    expect(lib().localScope).toBeInstanceOf(Object);
    expect(lib().globalScope).toBeInstanceOf(Object);
});

test('Mock global Object and use in local scope', function() {
    global.Object.create = false;

    expect(lib().localScope).toBe('localScope');
});

test('Mock global Object and use in global scope', function() {
    global.Object.create = false;

    expect(lib().globalScope).toBe('globalScope');
});

});

```bash
> jest

 FAIL  ./test.js
  ● Test mocking global variable › Mock global Object and use in global scope

    expect(received).toBe(expected)

    Expected value to be (using ===):
      "globalScope"
    Received:
      [Function create]

    Difference:

      Comparing two different types of values. Expected string but received function.

      at Object.<anonymous> (test.js:19:35)
      at process._tickCallback (internal/process/next_tick.js:109:7)

  Test mocking global variable
    ✓ Native global Object (6ms)
    ✓ Mock global Object and use in local scope (1ms)
    ✕ Mock global Object and use in global scope (4ms)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 2 passed, 3 total

https://github.com/ilyar/sandbox/tree/master/jest

Most helpful comment

if you want to mock globals before you require the module you'll need to do something like this:
```js

beforeEach(jest.resetModules);

test('111', () => {
global.myVariable = 111;
const lib = require('./lib');
// ...
});

All 5 comments

i think that's expected. since you first require your ./lib file (that captures the value of Object.create at the moment when you require it) and then you try to mock your global variable (which should not change anything, since globalScope variable is already defined inside your lib module

if you want to mock globals before you require the module you'll need to do something like this:
```js

beforeEach(jest.resetModules);

test('111', () => {
global.myVariable = 111;
const lib = require('./lib');
// ...
});

No need to re-open but is there an official statement saying we can't replace global.window?
Let's say I need to test:

/**
 * Get browser location or an empty location
 * @returns {Object} A window location if code is executing client-side, or empty query string if not.
 */
export default () => (
    global.window ?
        global.window.location :
        { search: '' }
);

It always returns an empty search string because the test fails to overwrite global.window:

describe('selector getLocation', () => {

    beforeEach(jest.resetModules);

    test('should return window location in browser', () => {
        global.window.location = { search: '?test' };
        const getLocation = require('../../src/selectors/getLocation').default;
        expect(getLocation().search).toEqual('?test');
    });

    test('should return empty search otherwise', () => {
        global.window = null;
        const getLocation = require('../../src/selectors/getLocation').default;
        expect(getLocation().search).toEqual('');
    });
});

Can anything be done to make it work other than excluding the file from test coverage?

Figured it out, replacing window doesn't work, but you can use Object.defineProperty to replace its location or another member, or delete global.window to get rid of it entirely.

global.window.x = y doesn't work
global.window = null doesn't work

Was this page helpful?
0 / 5 - 0 ratings