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