如何使用玩笑模拟构造函数实例化 class 实例?

How to mock a constructor instantiated class instance using jest?

给定实例化并使用另一个 class Logger 的 class Person,如何验证 Logger 的方法何时被调用Person在下面的例子中被实例化了?

// Logger.ts
export default class Logger {
    constructor() {}
    log(m: String) {
        console.log(m);

        // Other operations that are outside testing (e.g., file write).
        throw Error('error');
    }
}

// Person.ts
import Logger from "./Logger";
export default class Person {
    constructor() {
        const logger = new Logger();
        logger.log('created');
    }
    // ...
}

// Person.test.ts
import Person from "./Person";
import Logger from "./Logger";
describe('Person', () => {
    it('calls Logger.log() on instantiation', () => {
        const mockLogger = new Logger();
        getCommitLinesMock = jest
            .spyOn(mockLogger, 'log')
            .mockImplementation(() => {});

        new Person(); // Should call Logger.log() on instantiation.

        expect(getCommitLinesMock).toBeCalled();
    });
});

一个选项是将 Logger 作为构造函数参数传递,如下所示:

class Person {
    constructor(logger: Logger) {
        logger.log('created');
    }
    // ...
}

但是,有没有其他方法不改变构造函数就可以完成测试呢?

您可以使用 jest.mock(moduleName, factory, options),它会自动模拟给定模块的所有导出。

所以你可以做 jest.mock("./Logger")Logger 构造函数及其所有方法将被模拟函数替换(that return undefined默认情况下),现在您可以监视构造函数及其所有方法的行为。

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

jest.mock("./Logger");

describe("Person", () => {
  it("calls the Logger constructor on instantiation", () => {
    new Person();
    expect(Logger).toHaveBeenCalledTimes(1);
  });
});

所有模拟函数都有一个特殊的 .mock 属性,其中提供了与模拟函数相关的各种数据,包括模拟构造函数在使用 [=16 调用时创建的实例=].

因此,由 mock Logger 创建的所有实例都保存在 Logger.mock.instances 中,您可以使用它来监视方法调用。

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

jest.mock("./Logger");

describe("Person", () => {
  it("calls the Logger constructor and the log method on instantiation", () => {
    new Person();
    expect(Logger).toHaveBeenCalledTimes(1);
    const mockLoggerInstance = Logger.mock.instances[0];
    const mockLogMethod = mockLoggerInstance.log;
    expect(mockLogMethod).toHaveBeenCalledTimes(1);
  });
});