在@testing-library/react 中发现可重现的异步错误

Reproducable asynchronous bug found in @testing-library/react

除非我弄错了,否则我相信我发现了 @testing-library/react 包如何触发(或在本例中不是)重新呈现的错误。我有一个 codesandbox,您可以在几秒钟内下载和复制它:

https://codesandbox.io/s/asynchronous-react-redux-toolkit-bug-8sleu4?file=/README.md

作为此处的总结,我刚刚获得了一个 redux 存储,并且在挂载中进行了一些异步 activity 之后,我将布尔值从 false 切换为 true useEffect 在组件中:

import React, { useEffect } from "react";
import { useAppDispatch } from "../hooks/useAppDispatch";
import { setMyCoolBoolean } from "../redux/slices/exampleSlice";
import AnotherComponent from "./AnotherComponent";

export default function InnerComponent() {
  const dispatch = useAppDispatch();

  const fetchSomeData = async () => {
    await fetch("https://swapi.dev/api/people");
    dispatch(setMyCoolBoolean(true));
  };

  // on mount, set some values
  useEffect(() => {
    fetchSomeData();
  }, []);

  return <AnotherComponent />;
}

然后,在另一个组件中,我使用 useAppSelector 钩子连接到该存储值,然后 useEffect 在那里做一些本地的事情(愚蠢的例子,但它说明了我的观点。):

import { useEffect, useState } from "react";
import { useAppSelector } from "../hooks/useAppSelector";

export default function AnotherComponent() {
  const { myCoolBoolean } = useAppSelector((state) => state.example);

  const [localBoolean, setLocalBoolean] = useState(false);

  // when myCoolBoolean changes, set the local boolean state value in this component
  // somewhat a dumb example but it illustrates
  // the failure of react-testing-library
  useEffect(() => {
    // only do something in this component
    // if myCoolBoolean changes to true
    if (myCoolBoolean) {
      console.log("SET TO TRUE!");
      setLocalBoolean(myCoolBoolean);
    }
  }, [myCoolBoolean]);

  if (localBoolean) {
    return <span data-testid="NEW">I'm new</span>;
  }
  return <span data-testid="ORIGINAL">I'm original</span>;
}

结果,我的测试想看​​看是否显示了 'new' 值。尽管发出 rerender,您将看到测试失败:

import React from "react";
import { render } from "@testing-library/react";
import App from "../../src/App";
import { act } from "react-test-renderer";
import 'whatwg-fetch'

test("On mount, boolean value changes, causing our new span to show up", async () => {
  const { getByTestId, rerender } = render(<App />);
  await act(async () => {
    // expect(getByTestId("ORIGINAL")).toBeTruthy();

    // No matter how many times you call rerender here, 
    // you'll NEVER see the "NEW" test id (and thus corresponding <span> element) appear in the document
    // despite this being the case in any standard browser
    await rerender(<App />);

    // If you comment this line below out, the test passes fine. 
    // test ID "ORIGINAL" is found, but "NEW" is never found!!!!
    expect(getByTestId("NEW")).toBeTruthy();
  });
});

行为在浏览器中完全符合预期,但在我的玩笑测试中失败了。谁能指导我如何通过考试?据我所知,我的 React 组件和 Redux 的代码和实现是目前最干净的最佳实践,所以我更希望这是我对 @testing-library 工作方式的严重误解,虽然我认为 rerender 会成功。

我显然误解了 react-testing-library 的工作原理。您甚至根本不需要使用 rerenderact!简单地使用 waitForawait / async 就足以触发挂载逻辑和后续渲染:

import React from "react";
import { findByTestId, render, waitFor } from "@testing-library/react";
import App from "../../src/App";
import { act } from "@testing-library/react-hooks/dom";
import "whatwg-fetch";

test("On mount, boolean value changes, causing our new span to show up", async () => {
  const { getByTestId, rerender, findByTestId } = render(<App />);
  // Works fine, as we would expect
  expect(getByTestId("ORIGINAL")).toBeTruthy();
  
  // simply by using 'await' here, react-testing-library must rerender somehow
  // note that 'act' isn't even used or needed either!
  await waitFor(() => getByTestId("NEW"));
});

又一个“想太多”的案例坏了...