How to mock instance methods of a class mocked with jest.mock?

sur*_*s02 5 javascript unit-testing mocking jestjs

How can the instance methods be mocked for a class that is being mocked with jest.mock?

For example, a class Logger is mocked:

import Person from "./Person";
import Logger from "./Logger";

jest.mock("./Logger");

describe("Person", () => {
  it("calls Logger.method1() on instantiation", () => {
    Logger.method1.mockImplementation(() => {}) // This fails as `method1` is an instance method but how can the instance method be mocked here?
    new Person();
    
    expect(Logger.method1).toHaveBeenCalled();
  });
});
Run Code Online (Sandbox Code Playgroud)

SSM*_*SSM 4

自动模拟

除非使用目录指定手动模拟,否则调用jest.mock会自动模拟被模拟模块的所有导出__mocks__

因此,这一行jest.mock("./Logger")自动用模拟函数替换了Logger构造函数及其所有方法,使我们能够测试这些函数的行为方式。

并且与创建的实例相关的信息Logger保存在 中Logger.mock.instances,因此我们可以使用它来测试方法是否被正确调用。

import Person from "./Person";
import Logger from "./Logger";

jest.mock("./Logger");

describe("Person", () => {
  it("calls method1 on instantiation", () => {
    const p = new Person();
    // Logger constructor should have been called
    expect(Logger).toHaveBeenCalled();
    
    const mockLoggerInstance = Logger.mock.instances[0];
    const mockMethod1 = mockLoggerInstance.method1;
    // method1 should have also been called
    expect(mockMethod1).toHaveBeenCalled();
  });
});
Run Code Online (Sandbox Code Playgroud)

使用模块工厂参数

您还可以通过将工厂函数作为第二个参数传递给 来显式提供模块工厂jest.mock。因此,现在将使用提供的模块工厂而不是 Jest 的自动模拟功能。请参阅文档以获取更多信息。

import Person from "./Person";
import Logger from "./Logger";

const mockMethod1 = jest.fn();
jest.mock("./Logger", () =>
  jest.fn().mockImplementation(() => ({
    method1: mockMethod1,
  }))
);

describe("Person", () => {
  it("calls method1 on instantiation", () => {
    const p = new Person();
    // Logger constructor should have been called
    expect(Logger).toHaveBeenCalled();
    // method1 should have also been called
    expect(mockMethod1).toHaveBeenCalled();
  });
});
Run Code Online (Sandbox Code Playgroud)

注意: jest.mock()调用被提升,因此您不能先定义变量,然后在工厂函数中使用它,除非该变量以mock. 正因为如此,我们才能进入mockMethod1工厂内部。

手动模拟

您可以通过创建位于 的手动模拟来实现与模块工厂函数类似的行为__mocks__/Logger.js。现在,只需调用 .mock 实现即可跨测试文件使用jest.mock

// __mocks__/Logger.js
const mockMethod1 = jest.fn();
const mockLogger = jest.fn(() => ({
  method1: mockMethod1,
}));
Run Code Online (Sandbox Code Playgroud)

用法与模块工厂函数类似,但您现在还必须在测试中导入模拟方法。

注意:您仍然需要使用原始模块路径,不要包含__mocks__.

import Person from "./Person";
import Logger, { mockMethod1 } from "./Logger";

jest.mock("./Logger");

describe("Person", () => {
  it("calls method1 on instantiation", () => {
    const p = new Person();
    // Logger constructor should have been called
    expect(Logger).toHaveBeenCalled();
    // method1 should have also been called
    expect(mockMethod1).toHaveBeenCalled();
  });
});
Run Code Online (Sandbox Code Playgroud)