Home >Web Front-end >JS Tutorial >Jest Recap: Safely Mock Properties and Methods of Global Objects

Jest Recap: Safely Mock Properties and Methods of Global Objects

WBOY
WBOYOriginal
2024-07-19 15:10:32342browse

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);
    });
});

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);
    });
});

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);
    });
});

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();
    });
});

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);
    });
});

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();
    });
});

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.

The above is the detailed content of Jest Recap: Safely Mock Properties and Methods of Global Objects. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Previous article:Map in JSNext article:Map in JS