网站搜索

javascript - 如何使用 jest 模拟通过构造函数实例化的类实例?


假设有一个类 Person 实例化并使用另一个类 Logger,如何验证在下面的例子中,当 Person 被实例化时,Logger 的方法被调用?

// 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');
    }
    // ...
}

但是,有没有其他方法可以在不更改构造函数的情况下完成测试?

答案#1

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

所以你可以使用 jest.mock("./Logger"),Logger 构造函数及其所有方法都会被替换为模拟函数(默认返回 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 属性,其中包含与模拟函数相关的各种数据,包括模拟构造函数创建的实例,以及使用 new 调用它的时间。

因此,由模拟 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);
  });
});

答案#2

测试一个带有方法的类的另一种方式是在模拟中使用 原型。

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


jest.mock("./Person");

describe("Person", () => {

 it("mock and test Person method", async () => {

  jest.spyOn(Person.prototype, "somePersonMethod").mockImplementation(() => {
      return "testing Person class method";
   });
    
    //expect(...

 });

});