1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

react-testing-libraryで『An update to App inside a test was not wrapped in act(...).』エラー

Posted at

はじめに

jest + react-testing-libraryでのテストを実行中、表題のエラーが発生しました。

問題

ボタンをクリックするイベントをテスト実行すると表題のエラーになる。

An update to App inside a test was not wrapped in act(...).
日本語訳:テスト内のAppの更新がact(...)でラップされていなかった。

image.png

問題が発生したコード

AppComponetnt.spec.tsx
import App from "../App";
import { render, screen } from "@testing-library/react";

describe("title", () => {
  it("登録ができる", async () => {
    render(<App />);
    // 登録ボタンの要素を見つける
    const newRecordButton = await screen.findByTestId("new-record-button");
    // 登録ボタンクリック
    newRecordButton.click();
  });
});

解決方法

1.actを使う

ドキュメントより引用

UI テストを書く際、レンダー、ユーザイベント、データフェッチなどのタスクは、ユーザインターフェースにおける「操作単位 (unit of interaction)」と捉えることができます。React は act() というヘルパを提供しており、これによりこれらの「操作単位」に関連するすべての更新が処理されて DOM に適用された後に、アサーションを行えるようになります。

  • ボタンをクリックすると、Reactは状態の更新を検知し、再レンダリングが行われます
  • act() を使わない場合、再レンダリングを行っている際に次のプロセスに進もうとし、エラーになってしまいます
  • act() を使うことで、操作単位(今回の場合は、ボタンをクリックする操作)を行った後、次のプロセスに進むことができます
AppComponetnt.spec.tsx
import App from "../App";
-import { render, screen } from "@testing-library/react";
+import { fireEvent, render, screen, act } from "@testing-library/react";

describe("title", () => {
  it("登録ができる", async () => {
    render(<App />);
    // 登録ボタンの要素を見つける
    const newRecordButton = await screen.findByTestId("new-record-button");
    // 登録ボタンクリック
-    newRecordButton.click();
+    act(() => {
+      newRecordButton.click();
+    });
  });
});

2.fireEventを使う

fireEventについてドキュメントを確認すると以下の記述がありました

act wrapper around react act; React Testing Library wraps render and fireEvent in a call to act already so most cases should not require using it manually

日本語訳:リアクト・テスティング・ライブラリは、renderとfireEventをすでにactの呼び出しでラップしているので、ほとんどの場合、手動で使う必要はないはずだ。

fireEventを使用する場合、内部でactを使用しているため、actを使う必要がないと理解しました。
fireEventを使用した場合もエラーとならずテスト実行をすることができました。

AppComponetnt.spec.tsx
import App from "../App";
import { fireEvent, render, screen, act } from "@testing-library/react";

describe("title", () => {
  it("登録ができる", async () => {
    render(<App />);
    // 登録ボタンの要素を見つける
    const newRecordButton = await screen.findByTestId("new-record-button");
    // 登録ボタンクリック
-    act(() => {
-     newRecordButton.click();
      fireEvent.click(newRecordButton);
-    });
  });
});

3.userEventを使う

user-event allows you to describe a user interaction instead of a concrete event. It adds visibility and interactability checks along the way and manipulates the DOM just like a user interaction in the browser would. It factors in that the browser e.g. wouldn't let a user click a hidden element or type in a disabled text box.
This is why you should use user-event to test interaction with your components.

日本語訳:user-eventは、具体的なイベントの代わりにユーザーとのインタラクションを記述することができます。その過程で可視性とインタラクタビリティのチェックを追加し、ブラウザでのユーザーインタラクションのようにDOMを操作します。ブラウザは、例えば、ユーザーが非表示の要素をクリックしたり、無効化されたテキストボックスに入力したりすることを許しません。
これが、コンポーネントとのインタラクションをテストするためにuser-eventを使うべき理由です。

fireEventよりもよりユーザーの操作に近いテストを実現できると理解しました。

AppComponetnt.spec.tsx
import App from "../App";
import { fireEvent, render, screen, act } from "@testing-library/react";

describe("title", () => {
  it("登録ができる", async () => {
+   const user = userEvent.setup();
    render(<App />);
    // 登録ボタンの要素を見つける
    const newRecordButton = await screen.findByTestId("new-record-button");
    // 登録ボタンクリック
-   fireEvent.click(newRecordButton);
+   user.click(newRecordButton);
  });
});

おわりに

actでラップしてくださいのエラーから、芋づる式で様々な解消方法が判明したことは何気に嬉しいです。
ドキュメントによると実際のユーザー操作に近いuserEventを使用することを推奨していました。

参考

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?