如何模拟用 jest.mock 模拟的 class 的实例方法?

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

如何为使用 jest.mock 模拟的 class 模拟实例方法?

比如一个classLogger被mock了:

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

自动模拟

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

因此,这一行 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();
  });
});

使用模块出厂参数

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

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

注意: jest.mock()调用被提升,所以你不能先定义一个变量,然后在工厂函数中使用它,除非变量以[=22为前缀=].因此我们可以访问工厂内部的 mockMethod1

手动模拟

您可以通过创建位于 __mocks__/Logger.js 的手动模拟来实现与模块工厂函数类似的行为。现在这个模拟实现可以通过简单地调用 jest.mock.

跨测试文件使用
// __mocks__/Logger.js
const mockMethod1 = jest.fn();
const mockLogger = jest.fn(() => ({
  method1: mockMethod1,
}));

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

注意:还是要使用原来的模块路径,不要包含__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();
  });
});