ZU TECH BLOG

useEffectを使用したページのテストについて

はじめに

こんにちは、@zsp2088devです。
最近、useEffectを使用したページのテストが、うまくできずにハマってしまったことがありました。
解決方法を忘れないために、記事にまとめておきます。

動作環境

本記事で使用したコードの動作環境です。
簡単に環境を作りたかったので、nextを使用しています。

  • next 12.2.3
  • react 18.2.0
  • @testing-library/jest-dom 5.16.4
  • @testing-library/react 13.3.0
  • jest 28.1.3
  • jest-environment-jsdom 28.1.3
  • jest-fetch-mock 3.0.3

使用したコード

今回使用したコードは次のとおりです。
サンプルデータとして、JSONPlaceholderからTODOを取得して、それのタイトルを表示します。

// sample1.jsx

import { useEffect, useState } from "react";

export default function Sample1() {
  const [title, setTitle] = useState("initial value");

  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/todos/1")
      .then((response) => response.json())
      .then((json) => setTitle(json.title));
  });

  return (
    <>
      <p>{title}</p>
    </>
  );
}

jest-fech-mockを導入する

fetch関数のモック化に、json-fetch-mock を使用しました。
本記事と同じ環境でjest-fetch-mockを使用するときは、テストコードの先頭に下記を記述する必要があるようです。

import { enableFetchMocks } from "jest-fetch-mock";
enableFetchMocks();

fetch関数をモック化する

下記のようにテストコードに記述して、fetch関数をモック化します。

fetchMock.mockResponse(
  JSON.stringify({
    userId: 1,
    id: 1,
    title: "delectus aut autem",
    completed: false,
  })
);

useEffectにある関数をモック化する

useEffectにある関数の実行を待つために、act()を使用します。
act()を使用しないとうまく動作しないため、注意する必要があります。

詳細については、こちらを参照してください。

await act(async () => {
  render(<Sample1 />);
});

使用したテストコード

以上を踏まえた上で、今回使用したテストコードは次のとおりです。
これを実行すると、テストに成功します。

// sample1.test.jsx

import { enableFetchMocks } from "jest-fetch-mock";
enableFetchMocks();

import fetchMock from "jest-fetch-mock";
import { render, screen, waitFor } from "@testing-library/react";
import Sample1 from "../pages/sample1";
import { act } from "react-dom/test-utils";
import "@testing-library/jest-dom";

describe("sample test", () => {
  test("test", async () => {
    fetchMock.mockResponse(
      JSON.stringify({
        userId: 1,
        id: 1,
        title: "delectus aut autem",
        completed: false,
      })
    );

    await act(async () => {
      render(<Sample1 />);
    });

    expect(screen.getByText("delectus aut autem")).toBeInTheDocument();
  });
});

おわりに

本記事では、useEffectを使用したページのテスト方法についてまとめました。
フロントエンドのテストは、「大変だなー」と思うことが多いです。
これからも解決できたことがあれば記事にしていこうと思います。

追記

act()を使用せずにfindByText()を使用すると、本記事のテストコードを下記のように置き換えることができます。
ドキュメントによると、findByはDOMの変更がすぐに行われないときに有効であると書かれているため、今回のようなケースに適していると言えます。

render(<Sample1 />);
expect(await screen.findByText("delectus aut autem")).toBeInTheDocument();

ただし、これを実行したときに、「状態を変更するときはactでラップしたほうがよい」と警告されました。
警告が気になるようであれば、本文でまとめた書き方にするとよさそうです。

Warning: An update to Sample1 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 */

This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act

© 2022 zsp2088dev