2
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?

More than 1 year has passed since last update.

Jest + React-Testing-Libraryのコードまとめ

Last updated at Posted at 2022-06-15

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を付与して取得

HTML
<span data-testid="copyright">@React</span>
JavaScript
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をインストール

コマンドプロンプト or ターミナル
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のテスト

◎必要なモジュールのインポート

コマンドプロンプト or ターミナル
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

2
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
2
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?