useFakeTimers 在 jest/testing-library 中不起作用

cre*_*lus 7 javascript reactjs jestjs react-testing-library react-hooks

我正在渲染一个元素,该元素利用 setTimeout 将内部文本从加载状态更改为所需的消息:

function Message({ message }: any) {
  const [showMessage, setShowMessage] = useState(false);

  useEffect(() => {
    const CTATimer = setTimeout(() => {
      setShowMessage(true);
    }, 1500);
    return () => {
      clearTimeout(CTATimer);
    };
  }, []);

  if (!showMessage) {
    return <p>Loading...</p>;
  }

  return (
    <>
      <div>{message.text}</div>
    </>
  );
}
Run Code Online (Sandbox Code Playgroud)

相应的测试渲染,然后将时间提前 1500 毫秒,然后应该显示消息。然而,目前测试失败,终端显示文本仍然是Loading...。测试是这样写的:

const mockMessage = {
  text: "this is a message",
  answers: [],
  id: 1,
};

afterEach(() => {
  jest.useRealTimers();
});

it("should show message after setTimeout", () => {
  jest.useFakeTimers();
  jest.advanceTimersByTime(1500);
  customRender(<Message message={mockMessage} />); // my customRender is just the default render but with a ThemeProvider wrapper.
  const message = screen.getByText(/this is a message/i);
  expect(message).toBeInTheDocument();
});
Run Code Online (Sandbox Code Playgroud)

为什么我的测试在 1500 毫秒过去后仍然呈现加载状态?

san*_*ooh 7

如果使用异步测试,因为您需要用于userEvent打字等。我在此博客上找到了一个解决方案:https ://onestepcode.com/testing-library-user-event-with-fake-timers/

诀窍是将delay选项设置userEventnull

const user = userEvent.setup({ delay: null });
Run Code Online (Sandbox Code Playgroud)

这是一个完整的测试用例

test("Pressing the button hides the text (fake timers)", async () => {
    const user = userEvent.setup({ delay: null });
    jest.useFakeTimers();
    
    render(<Demo />);

    const button = screen.getByRole("button");
    await user.click(button);

    act(() => {
        jest.runAllTimers();
    });

    const text = screen.queryByText("Hello World!");
    expect(text).not.toBeInTheDocument();

    jest.useRealTimers();
});
Run Code Online (Sandbox Code Playgroud)

  • 谢谢,对于使用“@testing-library/user-event”的任何人来说,这都应该被取消 (2认同)

sli*_*wp2 5

您应该在渲染组件后提前计时器。此外,您应该调用jest.advanceTimersByTime()内部act函数。否则,它会抛出警告:Warning: An update to Message inside a test was not wrapped in act(...).

\n

index.tsx:

\n
import React from \'react\';\nimport { useEffect, useState } from \'react\';\n\nexport function Message({ message }: any) {\n  const [showMessage, setShowMessage] = useState(false);\n\n  useEffect(() => {\n    const CTATimer = setTimeout(() => {\n      setShowMessage(true);\n    }, 1500);\n    return () => {\n      clearTimeout(CTATimer);\n    };\n  }, []);\n\n  if (!showMessage) {\n    return <p>Loading...</p>;\n  }\n\n  return (\n    <>\n      <div>{message.text}</div>\n    </>\n  );\n}\n
Run Code Online (Sandbox Code Playgroud)\n

index.test.tsx:

\n
import React from \'react\';\nimport { render, screen, act } from \'@testing-library/react\';\nimport \'@testing-library/jest-dom/extend-expect\';\nimport { Message } from \'./\';\n\ndescribe(\'Message\', () => {\n  const mockMessage = {\n    text: \'this is a message\',\n    answers: [],\n    id: 1,\n  };\n\n  afterEach(() => {\n    jest.useRealTimers();\n  });\n\n  it(\'should show message after setTimeout\', () => {\n    jest.useFakeTimers();\n    render(<Message message={mockMessage} />);\n    act(() => {\n      jest.advanceTimersByTime(1500);\n    });\n    const message = screen.getByText(/this is a message/i);\n    expect(message).toBeInTheDocument();\n  });\n});\n
Run Code Online (Sandbox Code Playgroud)\n

测试结果:

\n
import React from \'react\';\nimport { useEffect, useState } from \'react\';\n\nexport function Message({ message }: any) {\n  const [showMessage, setShowMessage] = useState(false);\n\n  useEffect(() => {\n    const CTATimer = setTimeout(() => {\n      setShowMessage(true);\n    }, 1500);\n    return () => {\n      clearTimeout(CTATimer);\n    };\n  }, []);\n\n  if (!showMessage) {\n    return <p>Loading...</p>;\n  }\n\n  return (\n    <>\n      <div>{message.text}</div>\n    </>\n  );\n}\n
Run Code Online (Sandbox Code Playgroud)\n