Is there any way in Jest to mock global objects, such as navigator, or Image*? I've pretty much given up on this, and left it up to a series of mockable utility methods. For example:

// Utils.js
export isOnline() {
    return navigator.onLine;
}

Testing this tiny function is simple, but crufty and not deterministic at all. I can get 75% of the way there, but this is about as far as I can go:

// Utils.test.js
it('knows if it is online', () => {
    const { isOnline } = require('path/to/Utils');

    expect(() => isOnline()).not.toThrow();
    expect(typeof isOnline()).toBe('boolean');
});

On the other hand, if I am okay with this indirection, I can now access navigator via these utilities:

// Foo.js
import { isOnline } from './Utils';

export default class Foo {
    doSomethingOnline() {
        if (!isOnline()) throw new Error('Not online');

        /* More implementation */            
    }
}

...and deterministically test like this...

// Foo.test.js
it('throws when offline', () => {
    const Utils = require('../services/Utils');
    Utils.isOnline = jest.fn(() => isOnline);

    const Foo = require('../path/to/Foo').default;
    let foo = new Foo();

    // User is offline -- should fail
    let isOnline = false;
    expect(() => foo.doSomethingOnline()).toThrow();

    // User is online -- should be okay
    isOnline = true;
    expect(() => foo.doSomethingOnline()).not.toThrow();
});

Out of all the testing frameworks I've used, Jest feels like the most complete solution, but any time I write awkward code just to make it testable, I feel like my testing tools are letting me down.

Is this the only solution or do I need to add Rewire?

*Don't smirk. Image is fantastic for pinging a remote network resource.


Solution 1:

As every test suite run its own environment, you can mock globals by just overwriting them. All global variables can be accessed by the global namespace:

global.navigator = {
  onLine: true
}

The overwrite has only effects in your current test and will not effect others. This also a good way to handle Math.random or Date.now.

Note, that through some changes in jsdom it could be possible that you have to mock globals like this:

Object.defineProperty(globalObject, key, { value, writable: true });

Solution 2:

Jest may have changed since the accepted answer was written, but Jest does not appear to reset your global after testing. Please see the testcases attached.

https://repl.it/repls/DecentPlushDeals

As far as I know, the only way around this is with an afterEach() or afterAll() to clean up your assignments to global.

let originalGlobal = global;
afterEach(() => {
  delete global.x;
})

describe('Scope 1', () => {
  it('should assign globals locally', () => {
    global.x = "tomato";
    expect(global.x).toBeTruthy()
  });  
});

describe('Scope 2', () => {
  it('should not remember globals in subsequent test cases', () => {
    expect(global.x).toBeFalsy();
  })
});