DEV Community

Hauke T.
Hauke T.

Posted on

1

Jest Recap: Safely Mock Properties and Methods of Global Objects

TL;DR:

  • You want to avoid that overridden/mocked properties/methods affect other tests.
  • For local objects (created and owned by this test) you can (and should) use
    • localObject.theAnswer = 42 and
    • localObject.calcTheAnswer = jest.fn(() => 42).
  • For global objects you should use
    • jest.replaceProperty(globalObject, "theAnswer", 42) and
    • jest.spyOn(globalObject, "calcTheAnswer").mockReturnValue(42).
  • Make sure jest.restoreAllMocks() is called in an afterEach() hook.

What?

In a perfect world codebase there is no need to manipulate global objects, but the world codebases are messy - and so is testing.

What you want to avoid at all costs, is for one test to affect the other. Tests should be meaningful regardless of their order or if some tests are skipped.

Mocking Properties

A naive approach to mock values is to just set the properties to whatever value you need in your test.
This is fine as long as you change values in local objects owned (created by) this specific test:

describe("override properties of local objects", () => {
    it("works and is harmless", () => {
        const myArray = [1];
        myArray.length = 0;
        expect(myArray).toHaveLength(0);
    });
    it("does not affect the next test", () => {
        const myArray = [1];
        expect(myArray).toHaveLength(1);
    });
});
Enter fullscreen mode Exit fullscreen mode

If you do that for global objects, it gets messy:

describe("don't override properties of global objects", () => {
    it("works before the property is overridden", () => {
        expect(window.innerWidth).toBeGreaterThan(0);
    });
    it("works, but is evil", () => {
        window.innerWidth = 0;
        expect(window.innerWidth).toBe(0);
    });
    it("fails in the test after the property was overridden", () => {
        expect(() => {
            expect(window.innerWidth).toBeGreaterThan(0); // <-- ERROR: expect(received).toBeGreaterThan(expected)
        }).toThrow(Error);
    });
});
Enter fullscreen mode Exit fullscreen mode

This is what jest.replaceProperty() was made for:

describe("use jest.replaceProperty() to override properties of global objects", () => {
    afterEach(() => {
        jest.restoreAllMocks();
    });
    it("works before the property is overridden", () => {
        expect(window.innerWidth).toBeGreaterThan(0);
    });
    it("works and is harmless", () => {
        jest.replaceProperty(window, "innerWidth", 0);
        expect(window.innerWidth).toBe(0);
    });
    it("does not affect the next test", () => {
        expect(window.innerWidth).toBeGreaterThan(0);
    });
});
Enter fullscreen mode Exit fullscreen mode

Mocking Methods

Methods can be mocked similar to properties.

describe("override methods of local objects using jest.fn()", () => {
    it("works and is harmless", () => {
        const mySet = new Set([1]);
        mySet.has = jest.fn().mockReturnValue(false);
        expect(mySet.has(1)).toBeFalsy();
    });
    it("does not affect the next test", () => {
        const mySet = new Set([1]);
        expect(mySet.has(1)).toBeTruthy();
    });
});
Enter fullscreen mode Exit fullscreen mode

If you use myObject.someFunction = jest.fn() on global objects, your tests may depend on each other and loose their meaning:

describe("don't override methods of global objects using jest.fn()", () => {
    it("works before the method is overridden", () => {
        expect(document.getElementById("foo")).toBeNull();
    });
    it("works, but is evil", () => {
        const el = document.createElement("div");
        document.getElementById = jest.fn().mockReturnValue(el);

        expect(document.getElementById("foo")).toBe(el);
    });
    it("fails in the test after the property was overridden", () => {
        expect(() => {
            expect(document.getElementById("foo")).toBeNull(); // <-- ERROR: expect(received).toBeNull()
        }).toThrow(Error);
    });
});
Enter fullscreen mode Exit fullscreen mode

How should we mock methods in global objects? That is what jest.spyOn() is good for:

describe("use jest.spyOn() to override methods of global objects", () => {
    afterEach(() => {
        jest.restoreAllMocks();
    });
    it("works before the method is overridden", () => {
        expect(document.getElementById("foo")).toBeNull();
    });
    it("works and is harmless", () => {
        const el = document.createElement("div");
        jest.spyOn(document, "getElementById").mockReturnValue(el);

        expect(document.getElementById("foo")).toBe(el);
    });
    it("does not affect the next test", () => {
        expect(document.getElementById("foo")).toBeNull();
    });
});
Enter fullscreen mode Exit fullscreen mode

You Must Clean up

If you want to be sure all tests find the system in the same (fresh, clean) state, you need to restore the state of mocks after each test.

The simplest solution is to set the restoreMocks configuration property.

The most straight forward option is to call jest.restoreAllMocks() in the afterEach()

How to Mock Something for all Tests

Sometimes you want to Mock things for all tests in a file. If you use jest.spyOn() and jest.replaceProperty() on the top-level or in a describe() block, all Mocks will be reset after the first test is executed.

On the top-level you can safely override properties and methods, without jest.spyOn() and jest.replaceProperty().

If you want to Mock things only for a describe() block, you need to make these calls in a beforeEach() hook.

SurveyJS custom survey software

JavaScript UI Libraries for Surveys and Forms

SurveyJS lets you build a JSON-based form management system that integrates with any backend, giving you full control over your data and no user limits. Includes support for custom question types, skip logic, integrated CCS editor, PDF export, real-time analytics & more.

Learn more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs