带有 userEvent.click 错误 act() 警告的 React 测试库

React Testing Library with userEvent.click wrong act() warning

我有几个用 Jest 和 React 测试库编写的测试。他们都模拟获取并使用 userEvent.click 调用来触发发出获取请求的提交按钮。状态在组件中得到更新,我做出断言。我正在使用 useEffect 挂钩来填充数据数组,但考虑到我将一个空的依赖项数组传递给它,它在初始加载时仅 运行s。我所有的测试目前都通过了。如果我 运行 它们全部在一起,我会得到一个错误的 act() 错误,该错误源于 useEffect:

Warning: It looks like you're using the wrong act() around your test interactions.
Be sure to use the matching version of act() corresponding to your renderer:

// for react-dom:
import {act} from 'react-dom/test-utils';
// ...
act(() => ...);

// for react-test-renderer:
import TestRenderer from react-test-renderer';
const {act} = TestRenderer;
// ...
act(() => ...);

但是,当我 运行 只有其中一个时,我没有收到警告。我可以 运行 他们中的任何一个,我没有得到任何警告。我只有在 运行 同时进行两项或多项测试时才会收到警告。

我的测试是:

describe("CartDetail", () => {
  test("Order is submitted when user clicks Place Order button.", async () => {
    global.fetch = jest.fn().mockImplementationOnce(() =>
      Promise.resolve({
        status: 200,
      })
    );
    
    renderComponent();

    await act(async () => {
      userEvent.click(await screen.findByRole("button", { name: "Place Order" }));
    });

    expect(screen.queryByText("Your meal order was successfully processed.")).toBeInTheDocument();
  });

  test("Error message is displayed to user when order fails with a 400.", async () => {
    global.fetch = jest.fn().mockImplementationOnce(() =>
      Promise.resolve({
        status: 400,
      })
    );
    
    renderComponent();

    await act(async () => {
      userEvent.click(await screen.findByRole("button", { name: "Place Order" }));
    });

    expect(screen.queryByText("Please confirm that you are ordering at least one of each meal you have in your cart.")).toBeInTheDocument();
    userEvent.click(screen.getByLabelText("Close alert"));
  });

  test("Error message is displayed to user when API fails.", async () => {
    global.fetch = jest.fn().mockRejectedValueOnce(() =>
      Promise.reject({
        status: 500,
      })
    );
    
    renderComponent();

    await act(async () => {
      userEvent.click(await screen.findByRole("button", { name: "Place Order" }));
    });

    expect(screen.queryByText("Your order failed.")).toBeInTheDocument();
    userEvent.click(screen.getByLabelText("Close alert"));
  });
});

我读到您不必将 userEvent 包装在 act() 中,因为它已经在后台。但是,如果我不实际包装它,我的测试会失败并抛出:

Warning: An update to CartDetail inside a test was not wrapped in act(...).
    
When testing, code that causes React state updates should be wrapped into act(...):
    
act(() => {
  /* fire events that update state */
});
/* assert on the output */

即使我注释掉我的断言,我的测试当然通过了,但我仍然收到错误的 act() 警告。问题直接来自:

await act(async () => {
  userEvent.click(await screen.findByRole("button", { name: "Place Order" }));
});

我不明白 useEffect 在初始加载时执行并且不再 运行,包括通过 userEvent.click() 单击按钮时,问题是如何产生的。我试过改用 waitFor() 并产生相同的精确结果。我已经搜索了互联网,没有什么比这更接近我了。 This GitHub thread 提到这是一个已知问题,但现在有点旧了,所以我不知道它是否仍然有效。

我终于找到了解决方法。 This GitHub post 提到在 act() 中包装 userEvent() 调用,我已经完成了。 Warning: It looks like you're using the wrong act() around your test interactions. 控制台错误的解决方法是在 userEvent() 调用之后和断言之前添加以下代码:

await new Promise((resolve) => setTimeout(resolve, 0));

我更新后的测试现在看起来像这样:

test("Order is submitted when user clicks Place Order button.", async () => {
    global.fetch = jest.fn().mockImplementationOnce(() =>
      Promise.resolve({
        status: 200,
      })
    );
    
    renderComponent();

    await act(async () => {
      userEvent.click(await screen.findByRole("button", { name: "Place Order" }));
    });

    await new Promise((resolve) => setTimeout(resolve, 0));

    expect(screen.queryByText("Your meal order was successfully processed.")).toBeInTheDocument();
  });

错误的 act() 警告现已消失。

我的问题与我在 act(...) 块中收到“使用 act(...)”错误类似,但修复略有不同。

根据这个 https://github.com/testing-library/user-event/issues/255#issuecomment-624847626 其中指出,

if you wrap userEvent calls with act, it should work. fireEvent from react-testing-library does this automatically, but fireEvent from dom-testing-library does not (because act is a react feature). user-event calls fireEvent from dom-testing-library, since this library is not react-specific.

所以我替换了 userEvent.click 返回 fireEvent.click,加上你使用宏任务等待微任务队列为空的修复 await new Promise(r => setTimeout(r)); 整个过程 终于 成功了。

我测试的其余部分使用 await ...find*(..) 而不是 getquery,并且始终 re-finds 一个 DOM 元素而不是将其放入变量,每个事件都通过辅助函数包装在 act 中……真是一团糟。

感觉@testing-library/react在算Reactre-renders什么的,而不是等React安定下来。我的工作测试失败了,只是在 already 异步的 onClick 处理程序中添加了一个愚蠢的 await Promise.resolve();。卧槽