开玩笑如何模拟打字稿 class 但仅在 1 个测试中改变一些方法行为而不是在其他测试中修改模拟

Jest how to mock a typescript class but alternate some method behavior in only 1 test and not modify mock in others

我有打字稿classuserInfo.ts:

export class UserInfo {
  public getName() {
    return "I am real name";
  }
}

我在 mocks 文件夹中有一个模拟的 class userInfo.ts:

export class UserInfo {
  public getName() {
    return "I am fake name";
  }
}

我有一个客户:

import { UserInfo } from "./userInfo";
export class Client {

  public functionToTest() {
    let validation = new UserInfo();
    return validation.getName();
  }

}

最后我想对此进行两次测试,在第一个中我只想为这个测试覆盖 getName 模拟,在第二个中我想要模拟 class 行为所以:

import { Client } from "./client";
import { UserInfo } from "./userInfo";

jest.mock("./userInfo");
const userInfoMocked = UserInfo as jest.MockedClass<typeof UserInfo>; // I tried with this but with no success

describe("Client", () => {

  it("should get Name", () => {
    let client = new Client();
    // UserInfo.prototype.getName = jest.fn().mockImplementationOnce(() => {
    //   return "Something weird happened";
    // });
    userInfoMocked.prototype.getName = jest.fn().mockImplementationOnce(() => {
      return "something weird happend";
    });

    // this is not working either
    // Property 'getName' does not exist on type 'MockedClass<typeof UserInfo>'.
    // userInfoMocked.getName = jest.fn().mockImplementationOnce(() => {
    //   return "something weird happend";
    // });

    let text = client.functionToTest();
    expect(text).toBe('something weird happend'); 
    let text2 = client.functionToTest();
    expect(text2).toBe('I am fake name'); // I get undefined (I overwrote prototype!)
  });

  it('should get fake name now', () => {
    let client = new Client();
    let text3 = client.functionToTest();
    expect(text3).toBe('I am fake name'); // I get undefined

  });
});

我很惊讶这样一个常见的(我认为)功能无法实现?如何在这方面取得成功?这甚至可能吗?

您可以为模拟分配一个默认实现:

import userInfo from "./userInfo"
jest.mock("./userInfo", () => ({
  getName: jest.fn(() => 'John Doe'),
}));

并且每次你想覆盖实现时:

userInfo.getName.mockImplementation(() => jest.fn().mockReturnValue('another value'));

如果您想在不同的测试中使用不同的模拟,请不要使用 mocks 文件夹。而是为每个测试创建您需要的模拟。 This 描述了您可以执行的不同类型的模拟。根据您的描述,我会使用 mockImplementation。例如,在一项测试中你可以做

UserInfo.mockImplementation(() => {
  return {
    getName: () => {
      return 'I am a real name'
    }
  }
}

在另一个测试中:

UserInfo.mockImplementation(() => {
  return {
    getName: () => {
      return 'I am a fake name'
    }
  }
}

所有方法都归结为同一件事,因此问题在于选择最适合您的代码结构且易于维护的方法。

这是我的解决方案: 它发生在不使用手动模拟时(因此在模拟下有 UserInfo.ts)它根本不起作用,但是当摆脱那个手动模拟并离开自动模拟时,我可以这样做:

import { mocked } from "ts-jest";
import { Client } from "./secretsManagerWrapper";
import { UserInfo } from "./userInfo";

jest.mock("./userInfo");
const userInfoMocked = mocked(UserInfo, false);

describe("Client", () => {
  it.only("should get Name", () => {
    let client = new Client();

    userInfoMocked.mockImplementationOnce(function () {
      return {
        getName: () => {
          return "John Doe Second";
        },
      };
    });

成功了!

我还发现当我想使用手动模拟时(在 mocks 文件夹下)我可以使用 Spy:

describe("Client", () => {
  it("should get Name", () => {
    let client = new Client();

    jest.spyOn(userInfoMocked.prototype, "getName").mockImplementationOnce(() => {
      return "John Doe Second";
    });

    let text = client.functionToTest();
    expect(text).toBe("John Doe Second");
    let text2 = client.functionToTest();
    expect(text2).toBe("I am fake name"); // I get what I want now
  });

  it("should get fake name now", () => {
    let client = new Client();
    let text3 = client.functionToTest();
    expect(text3).toBe("I am fake name"); // I get what I want now !
  });
});

而且我可以完成我想要的:)