0

目前,我正在学习如何使用 React 进行单元测试。但是,我想用 TypeScript 来学习它,所以本课程没有涵盖 TS 中出现的大多数错误。

我有一个使用 Mock Service Worker (msw) 配置的简单测试功能:

fit("sends username, email and password to backend after clicking the button", async () => {
  let requestBody;
  const server = setupServer(
    rest.post("/api/1.0/users", (req, res, ctx) => {
      requestBody = req.body;
      return res(ctx.status(200));
    })
  );
  server.listen();

  setupAll(); // Gets the elements on the page (like button)

  userEvent.click(button);

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

  expect(requestBody).toEqual({
    username: "LegacyUser",
    email: "legacy@user.com",
    password: "P455w0rd!",
  });
});

从理论上讲,这“有效”(它在测试列表中显示为通过),但在它上面会发生错误,例如:

console.error
  Warning: An update to SignUpPage 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 */

userEvent.click(button)因此,当我换行时act(() => userEvent.click(button)),它会一直显示此错误消息。

点击 userEvent 触发一个 onSubmit 处理程序:

const formSubmitHandler = (event: React.FormEvent<HTMLFormElement>) => {
  event.preventDefault();

  const { username, email, password } = formEntries;
  setIsApiInProgress(true);
  axios
    .post("/api/1.0/users", {
      username,
      email,
      password,
    })
    .then((response) => {
      if (response.status >= 200 && response.status < 300) {
        setIsSignUpSuccessfull(true);
        // console.log("OK", response.data);
        return response.data;
      }
      throw new Error(response.status.toString());
    })
    .catch(({ response }) => {
      console.log("CATCH", response);
    })
    .finally(() => {
      setIsApiInProgress(false);
    });
};

我在这里做错了什么?另外,有没有办法在不使用超时的情况下等待已解决的承诺?这种方式感觉有点hacky。

4

1 回答 1

0

这不是 Typescript 相关的问题。await new Promise((resolve) => setTimeout(resolve, 250));您需要将这一行包含在actie中await act(() => new Promise((resolve) => setTimeout(resolve, 250)))你可以在这里阅读更多关于它的信息。

与您的问题最相关的是这部分:

因此,来自 React 的行为警告是告诉我们,当我们没有预料到任何事情会发生时,我们的组件发生了一些事情。所以你应该把你和你的组件进行的每一次交互都包装起来,让 React 知道我们希望我们的组件执行一些更新,当你不这样做并且有更新时,React 会警告我们发生了意外更新. 这有助于我们避免上述错误。

幸运的是,对于你和我来说,React 会自动为你在 React 调用堆栈中运行的任何代码处理这个问题(比如点击事件,React 调用你的事件处理程序代码来更新组件),但它无法处理在外部运行的任何代码它是自己的调用堆栈(例如由于您正在管理的已解决承诺或您正在使用开玩笑的假计时器而运行的异步代码)。在这种情况下,您通常需要自己将其包装在 act(...) 或 async act(...) 中。但是,React 测试库具有自动包装在 act 中的异步实用程序!

请注意它如何特别提到如何为您处理点击事件(因此包装userEvent没有解决问题的原因)以及您自己管理的承诺如何未被处理(因此我们自己将其包装在行动中)。

你是对的,这是一种 hacky 方式。您可以使用 RTL 的异步方法,这些方法在他们的docs中有很好的解释。

这个:

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

  expect(requestBody).toEqual({
    username: "LegacyUser",
    email: "legacy@user.com",
    password: "P455w0rd!",
  });

变成这样:

await waitFor(() => {
  expect(requestBody).toEqual({
    username: "LegacyUser",
    email: "legacy@user.com",
    password: "P455w0rd!",
  });
})

于 2022-02-02T04:24:00.597 回答