创建 React App 在模拟异步函数时更改 jest.fn() 的行为

tra*_*jan 6 javascript async-await reactjs jestjs

jest.fn()我对从使用创建的干净 CRA 项目运行时的以下行为感到困惑npx create-react-app jest-fn-behaviour

例子:

describe("jest.fn behaviour", () => {
    
    const getFunc = async () => {
        return new Promise((res) => {
            setTimeout(() => {
                res("some-response");
            }, 500)
        });;
    }

    const getFuncOuterMock = jest.fn(getFunc);


    test("works fine", async () => {

        const getFuncInnerMock = jest.fn(getFunc);
        const result = await getFuncInnerMock();
        expect(result).toBe("some-response"); // passes
    })


    test("does not work", async () => {

        const result = await getFuncOuterMock();
        expect(result).toBe("some-response"); // fails - Received: undefined
    })

});
Run Code Online (Sandbox Code Playgroud)

上述测试将在干净的 JavaScript 项目中按预期工作,但在 CRA 项目中则不然。

有人可以解释为什么第二次测试失败吗?在我看来,当在jest.fn()非异步函数(例如describe上面)中调用时,模拟异步函数将无法按预期工作。仅当在异步函数(test上面)中调用时它才起作用。但为什么 CRA 会以这种方式改变行为呢?

jon*_*rpe 8

原因是,正如我在另一个答案中提到的,CRA 的默认 Jest 设置包括以下行

\n
    resetMocks: true,\n
Run Code Online (Sandbox Code Playgroud)\n

根据Jest 文档,这意味着(强调我的):

\n
\n

每次测试前自动重置模拟状态。相当于每次测试之前\n调用jest.resetAllMocks()这将导致任何模拟删除其虚假实现,但不会恢复其初始实现。

\n
\n

正如我在评论中指出的,您的模拟是在测试发现时创建的,当 Jest 查找所有规范并调用describe(但不是it/ test)回调时,而不是在执行时创建,当它调用规范回调时。因此,它的模拟实现是毫无意义的,因为它在任何测试运行之前就被清除了。

\n

相反,您有三个选择:

\n
    \n
  1. 正如您所看到的,在测试本身内创建模拟是可行的。在测试中重新配置现有的模拟也可以,例如getFuncOuterMock.mockImplementation(getFunc)(或只是getFuncOuterMock.mockResolvedValue("some-response"))。

    \n
  2. \n
  3. 您可以将模拟创建和/或配置移动到beforeEach回调中;这些在所有模拟重置执行:

    \n
    describe("jest.fn behaviour", () => {\n  let getFuncOuterMock;\n  // or `const getFuncOuterMock = jest.fn();`\n\n  beforeEach(() => {\n    getFuncOuterMock = jest.fn(getFunc);\n    // or `getFuncOuterMock.mockImplementation(getFunc);`\n  });\n\n  ...\n});\n
    Run Code Online (Sandbox Code Playgroud)\n
  4. \n
  5. resetMocks是 CRA 支持的用于覆盖 Jest 配置的键之一,因此您可以添加:

    \n
      "jest": {\n    "resetMocks": false\n  },\n
    Run Code Online (Sandbox Code Playgroud)\n

    进入你的package.json.

    \n

    但是,请注意,这可能会导致误报测试,由于在不同的expect(someMock).toHaveBeenCalledWith(some, args)测试中与模拟进行交互,您和它都会通过。如果您要禁用自动重置,您应该更改创建模拟的实现(即选项 2 中的示例),以避免测试之间的状态泄漏。beforeEachlet getFuncOuterMock;

    \n
  6. \n
\n

请注意,这与同步与异步无关,也与模拟生命周期无关;您会在 CRA 项目(或具有resetMocks: trueJest 配置的普通 JS 项目)中看到与以下示例相同的行为:

\n
describe("the problem", () => {\n  const mock = jest.fn(() => "foo");\n\n  it("got reset before I was executed", () => {\n    expect(mock()).toEqual("foo");\n  });\n});\n
Run Code Online (Sandbox Code Playgroud)\n
  \xe2\x97\x8f the problem \xe2\x80\xba got reset before I was executed\n\n    expect(received).toEqual(expected) // deep equality\n\n    Expected: "foo"\n    Received: undefined\n
Run Code Online (Sandbox Code Playgroud)\n