在@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 的工作原理。您甚至根本不需要使用 rerender
或 act
!简单地使用 waitFor
和 await
/ 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"));
});
又一个“想太多”的案例坏了...
除非我弄错了,否则我相信我发现了 @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 的工作原理。您甚至根本不需要使用 rerender
或 act
!简单地使用 waitFor
和 await
/ 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"));
});
又一个“想太多”的案例坏了...