Javascript Create React应用程序在模拟异步函数时更改jest.fn()的行为

Javascript Create React应用程序在模拟异步函数时更改jest.fn()的行为,javascript,reactjs,async-await,jestjs,Javascript,Reactjs,Async Await,Jestjs,当从使用npx create react app jest fn behavior创建的干净CRA项目运行时,我对jest.fn()的以下行为感到困惑 例如: describe("jest.fn behaviour", () => { const getFunc = async () => { return new Promise((res) => { setTimeout(() => {

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

例如:

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

});
上述测试将在干净的JavaScript项目中按预期工作,但在CRA项目中不会


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

原因是,正如我在中提到的,CRA的默认Jest设置包括:

resetMocks:true,
这意味着(我的重点):

在每次测试之前自动重置模拟状态。相当于 在每次测试之前调用jest.resetAllMocks()
这将导致 删除了假实现的所有模拟,但没有 恢复其初始实现

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

相反,您有三种选择:

  • 正如您所看到的,在测试本身内部创建模拟是可行的。在测试中重新配置现有的mock也会起作用,例如
    getFuncOuterMock.mockImplementation(getFunc)
    (或者只是
    getFuncOuterMock.mockResolvedValue(“一些响应”)

  • 您可以在每次回调之前将模拟创建和/或配置移动到
    中;在重置所有模拟后执行这些操作:

    description(“jest.fn行为”),()=>{
    让我们到最外面去;
    //或'const getFuncOuterMock=jest.fn()`
    在每个之前(()=>{
    getFuncOuterMock=jest.fn(getFunc);
    //或者“getFuncOuterMock.mockImplementation(getFunc)”`
    });
    ...
    });
    
  • resetMocks
    是覆盖Jest配置的CRA之一,因此您可以添加:

      "jest": {
        "resetMocks": false
      },
    
    进入您的
    包.json

    但是,请注意,这可能会导致假阳性测试,您
    期望(someMock).tobeencalledwith(some,args)
    ,并且由于在不同测试中与mock交互而通过。如果要禁用自动重置,还应更改实现以在每个
    之前在
    中创建模拟(即选项2中的
    let getFuncOuterMock;
    示例),以避免测试之间的状态泄漏

  • 请注意,这与sync vs.async或模拟生命周期以外的任何内容无关;在CRA项目(或带有
    resetMocks:true
    Jest配置的vanilla JS项目)中,您会看到以下示例的相同行为:


    “它在我的机器上运行得很好。”hoangdv很有趣。如果您在承诺中添加了
    setTimeout()
    (我已经更新了问题),只是为了确保这不是一个竞争条件问题,会怎么样?
    getTodosOuterMock
    可能在该描述块中的测试之间共享状态,并且是在测试发现而不是测试执行时创建的;也许当你试图减少这一点时,这方面就消失了?@jonrsharpe我承认这可能不是一个现实的例子。但是,如果我单独运行这些测试(依次对每个测试进行注释),我会得到相同的结果。我只是挠头想弄明白为什么
    async-await
    不适用于
    getTodosOuterMock
    函数,正如我所期望的那样,它仍然适用于我。请注意,在单元测试中,您可能并不希望有500毫秒的延迟,如果您正在测试实际使用setTimeout的东西,请查看Jest的“伪计时器”。
    describe("the problem", () => {
      const mock = jest.fn(() => "foo");
    
      it("got reset before I was executed", () => {
        expect(mock()).toEqual("foo");
      });
    });
    
      ● the problem › got reset before I was executed
    
        expect(received).toEqual(expected) // deep equality
    
        Expected: "foo"
        Received: undefined