JestとReact-Testing-Libraryで使うコードの一覧をまとめておきます。
長いので右下の目次から必要なところに飛んでください。
使用技術
・React(18)
・React-Testing-Library
・Jest
■画面取得まとめ
◎画面の状態を確認する
//画面全体
screen.debug();
//画面のx部分のみ
screen.debug(x)
◎タグ要素で指定(タグ名は下のGitHubに一覧あり)
//1つしかない場合
screen.getByRole("heading")
//複数ある時
screen.getAllByRole("button");
タグ名一覧はこちらより
[
[ '{"name": "article"}', [ 'article' ] ],
[ '{"name": "button"}', [ 'button' ] ],
[ '{"name": "td"}', [ 'cell', 'gridcell' ] ],
[ '{"name": "input", "attributes": [ {"name": "type", "value": "checkbox"}] }', [ 'checkbox' ] ],
[ '{"name": "th"}', [ 'columnheader' ] ],
[ '{"name": "select"}', [ 'combobox', 'listbox' ] ],
[ '{"name": "menuitem"}', [ 'command', 'menuitem' ] ],
[ '{"name": "dd"}', [ 'definition' ] ],
[ '{"name": "figure"}', [ 'figure' ] ],
[ '{"name": "form"}', [ 'form' ] ],
[ '{"name": "table"}', [ 'grid', 'table' ] ],
[ '{"name": "fieldset"}', [ 'group' ] ],
[ '{"name": "h1"}', [ 'heading' ] ],
[ '{"name": "h2"}', [ 'heading' ] ],
[ '{"name": "h3"}', [ 'heading' ] ],
[ '{"name": "h4"}', [ 'heading' ] ],
[ '{"name": "h5"}', [ 'heading' ] ],
[ '{"name": "h6"}', [ 'heading' ] ],
[ '{"name": "img"}', [ 'img' ] ],
[ '{"name": "a"}', [ 'link' ] ],
[ '{"name": "link"}', [ 'link' ] ],
[ '{"name": "ol"}', [ 'list' ] ],
[ '{"name": "ul"}', [ 'list' ] ],
[ '{"name": "li"}', [ 'listitem' ] ],
[ '{"name": "nav"}', [ 'navigation' ] ],
[ '{"name": "option"}', [ 'option' ] ],
[ '{"name": "input", "attributes": [ {"name": "type", "value": "radio"}] }', [ 'radio' ] ],
[ '{"name": "frame"}', [ 'region' ] ],
[ '{"name": "rel"}', [ 'roletype' ] ],
[ '{"name": "tr"}', [ 'row' ] ],
[ '{"name": "tbody"}', [ 'rowgroup' ] ],
[ '{"name": "tfoot"}', [ 'rowgroup' ] ],
[ '{"name": "thead"}', [ 'rowgroup' ] ],
[ '{"name": "th", "attributes": [ {"name": "scope", "value": "row"}] }', [ 'rowheader' ] ],
[ '{"name": "input", "attributes": [ {"name": "type", "value": "search"}] }', [ 'searchbox' ] ],
[ '{"name": "hr"}', [ 'separator' ] ],
[ '{"name": "dt"}', [ 'term' ] ],
[ '{"name": "dfn"}', [ 'term' ] ],
[ '{"name": "textarea"}', [ 'textbox' ] ],
[ '{"name": "input", "attributes": [ {"name": "type", "value": "text"}] }', [ 'textbox' ] ],
]
◎文字列で指定
//必ず存在する場合
screen.getByText("Udemy");
//存在するかどうかわからない場合の取得方法(nullを確認したい際など)
screen.queryByText("Udeeeemy");
◎テストIDを付与して取得
<span data-testid="copyright">@React</span>
screen.getByTestId("copyright");
◎Inputタグ
//placeholer名で取得
screen.getByPlaceholderText("Enter");
■expextのメソッドまとめ
//xが存在している
expect(x).toBeTruthy();
//xが存在していない、値がない
expect(x).toBeNull();
//x = yか否か(表示というより値の判断)
expect(x).toBe(y);
expect(x).toEqual(y);
//yがxに含まれているか
expect(x).toBeInTheDocument(y);
expect(x).toHaveTextContent(y);
//そのHTMLに属性(今回はdisabled)が存在するか否か
expect(x).toHaveAttribute("disabled");
■after, beforeメソッド
//そのテストファイルが始まる際、1度だけ発動
beforeAll(() => {});
//itが始まる度に発動
beforeEach(() => {});
//itが終わる度に発動
afterEach(() => {});
//そのテストファイルが終わる際、1度だけ発動
afterAll(() => {});
◎再レンダリングの前に一旦前のレンダリング情報を削除
import { render, screen, cleanup } from "@testing-library/react";
afterEach(() => cleanup());
◎疑似サーバ(API)を立てる際に必要
//このテストファイルを発動する前に、1度だけ発動する(今回は疑似サーバを立てる)
beforeAll(() => server.listen());
//itを使う度に発動:レンダリングデータの削除
afterEach(() => {
cleanup();
});
//このテストファイルが終わった後に発動:サーバを閉じる
afterAll(() => {
server.close();
});
■APIのモック
◎APIで取得するデータをダミーで作成する
エラー時の確認
・async, awaitをつけないとエラー時のテストが出来る
・その文字が無い場合のテストの際は「queryByText」で情報を取得すること
・取得した情報がnullである事を確認する(toBeNull)
it("データが入っていない場合、表示されないこと", () => {
render(<SampleComponent />);
//Iamを取得(/で囲むと部分一致検索) → nullならOK
const errorScreen = screen.queryByText(/I am/);
expect(errorScreen).toBeNull();
});
取得出来ている場合のテスト
・async, awaitをつけること
・「findByText」で取得
・toBeInTheDocumentで画面情報に「I am」が含まれているかを確認
it("データがきちんと取得出来ること", async () => {
render(<SampleComponent />);
//Iamを取得(/で囲むと部分一致検索) → 取得出来ればOK
const userDataScreen = await screen.findByText(/I am/);
expect(userDataScreen).toBeInTheDocument();
});
◎APIをダミーで作成する場合
事前準備
①mswをインストール
npm i -D msw@0.39.2
②インポート文準備
import React from "react";
import { render, screen, cleanup, act } from "@testing-library/react";
//ユーザのイベントをテストするため
import userEvent from "@testing-library/user-event";
//ダミーAPI作成用
import { rest } from "msw";
import { setupServer } from "msw/node";
//components
import SampleComponent from "./SampleComponent";
③before, after文記述
//このテストファイルを発動する前に、1度だけ発動する(今回は疑似サーバを立てる)
beforeAll(() => server.listen());
//itを使う度に発動:レンダリングデータの削除
afterEach(() => {
cleanup();
});
//このテストファイルが終わった後に発動:サーバを閉じる
afterAll(() => {
server.close();
});
疑似APIの作成
/**
* 指定したURLにアクセスすると、実際のURLでなく下記ダミーAPIにアクセスした事にする.
*/
const server = setupServer(
rest.get("http://dummyurl", (req, res, ctx) => {
//ステータスと返すデータの指定
return res(ctx.status(200), ctx.json({ username: "dummyName" }));
})
);
テストコード作成
テストするメソッドの中でsetState使っているとエラーになる
→setStateが発動するユーザのアクションをactで囲む必要がある
describe("Sampleコンポーネントテスト", () => {
it("APIを叩いて問題なく表示されること", async () => {
render(<SampleComponent />);
//ボタンをクリックしてAPIからデータを取得する
const buttonScreen = screen.getByRole("button");
// userEvent.click(buttonScreen); →これだとactで囲んでないのでダメ!
act(() => {
buttonScreen.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
//ユーザ情報が表示されているか否か
const userNameScreen = await screen.findByRole("heading");
screen.debug(userNameScreen);
expect(userNameScreen).toHaveTextContent("dummyName");
});
});
■Hooksのテスト
◎必要なモジュールのインポート
npm i @testing-library/react-hooks
npm i react-test-renderer
◎インポート文等を書く
import { render, screen, cleanup } from "@testing-library/react";
//hooksを使うのに必要!
import { act, renderHook } from "@testing-library/react-hooks";
//hooks
import { useSample } from "./useSample";
afterEach(() => cleanup());
◎Hooksの呼び出し
renderHooksを使うとHooksの呼び出しが出来る
const { result } = renderHook(() => useSample());
//resultにHooksの情報が入る
//result.current.変数名やメソッド名 で現在の変数等の状態を取得出来る
describe("Hooksテスト", () => {
it("初期値3が正しく表示されるか", () => {
//Hooksの利用
const { result } = renderHook(() => useCounter(3));
const initialResult = result.current.count;
expect(initialResult).toBe(3);
});
});
APIの時と同じように、メソッド内でsetStateが行われている際には
メソッドの発動タイミングをactで囲む必要がある
it("初期値3から1を引くメソッドのテスト", () => {
//Hooksの利用
const { result } = renderHook(() => useSample(3));
//メソッドの呼び出し
act(() => {
result.current.decrement();
});
//Hooksから変数「count」を呼び出し、値が2になっているかチェック
const decrementResult = result.current.count;
expect(decrementResult).toBe(2);
});
■act
テスト対象のメソッド内で「setState」使っている場合は
そのメソッドが発動するタイミングをactで囲む必要がある
import { act } from "react-dom/test-utils";
...
const buttonScreen = screen.getByRole("button");
//NG!
userEvent.click(buttonScreen);
//OK!
act(() => {
buttonScreen.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
◎Hooksのact
import { act } from "@testing-library/react-hooks";
//OK!:直接メソッドを呼ぶ
act(() => {
sampleMethod();
});
■まとめ
今回はこれ1ページ見ればとりあえずある程度のJestは網羅できるだろう、となるようにまとめてみました。フロントのテストってそもそも何をすればいいの?という方は下記も併せてご覧頂けると幸いです。
ここまでご覧いただきありがとうございました!
■おまけ
◎テストを試したGitHub
◎参考URL