在 react-testing-library 和 jest 中使用超时测试功能

我正在尝试遵循 TDD,并且我有一个 span 应该在 5 秒后出现在屏幕上。我根本没有实现跨度,所以测试应该失败,但目前它通过了测试 expect(messageSpan).toBeInTheDocument

这是我的两个测试:

it("doesn't show cta message at first", () => {
    render(<FAB />);
    const messageSpan = screen.queryByText(
      "Considering a career in nursing? Join our team!"
    );
    expect(messageSpan).toBeNull(); // passes, as it should
  });

  it("should show the cta message after 5 secs", () => {
    render(<FAB />);
    setTimeout(() => {
      const messageSpan = screen.getByText( // also tried queryByText instead of get
        "Considering a career in nursing? Join our team!"
      );
      expect(messageSpan).toBeInTheDocument(); // also passes, even though messageSpan should throw an error.
    }, 5000);
  });

这是我的 FAB 组件,您可以在其中看到根本没有消息:

export default function FAB() {
  return (
// using styled-components; there's no content in any of these.
    <StyledFABContainer>
      <StyledFABButton>
        <BriefcaseIcon />
      </StyledFABButton>
    </StyledFABContainer>
  );
}

更复杂的是,我不打算为 setTimeout 设置一个我调用的集合函数。我将在 5 秒的设定时间后简单地设置状态。所以不要认为我可以使用 jest 文档的计时器模拟部分中的建议: https://jestjs.io/docs/timer-mocks

我的两个问题是:

a)为什么这会通过而不抛出错误/空值? b) 如何正确测试 RTL 中的 setTimeout 功能?

更新:尝试使用 useFakeTimersactwaitFor 等的各种组合,但没有运气。这是我当前的测试,因为它被写出来,并抛出两个错误 - 一个说我需要在更改状态时使用 act (我是,但仍然如此),一个说我的 messageSpan 为空:

it("cta message to display after 5 secs", async () => {
    jest.useFakeTimers();
    const el = document.createElement("div");
    act(() => {
      ReactDOM.render(<FAB />, el);
    });
    jest.advanceTimersByTime(5000);
    const messageSpan = screen.queryByText(
      "Considering a career in nursing? Join our team!"
    );
    expect(messageSpan).toBeInTheDocument();
  });
stack overflow Test functionality with timeouts in react-testing-library and jest
原文答案

答案:

作者头像

您不能在测试中像这样使用 setTimeout。一个明显的原因是您不想在测试中等待 5 秒再继续。想象一个在 10 分钟后会发生变化的组件。你不能等那么久,但应该使用 jests mocked timers API 来代替。您可以推进 nodejs 计时器,以便您的组件更改,然后立即做出断言。关于您编写​​的测试的其他评论:

  • 如果您正在等待更改,您应该使用测试库中的 await waitFor(() => ...)。默认情况下,它每 50 毫秒检查一次期望值,总共 5 秒。如果所有期望都通过了,它会继续。您应该在那里进行异步断言。
  • “expect(messageSpan).toBeNull()”应该是“expect(messageSpan).not.toBeInTheDocument()”。使用测试库提供的可访问性方式很好。
作者头像

如果使用异步测试,我在此博客上找到了解决方案: https://onestepcode.com/testing-library-user-event-with-fake-timers/

诀窍是将 delay 上的 userEvent 选项设置为 null

const user = userEvent.setup({ delay: null });

这是一个完整的测试用例

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