Reactのテストについてメモ


Reactのテストで使うものたちがたくさんありすぎていつも混乱するのでメモ
JEST / React Testing Library / Apollo Client

とりあえず情報源を雑に貼っておく。
あとでまとめるかもしれないしまとめないかもしれない。

JEST

Javascriptでテストを書く時に使うフレームワーク
it / describe / expectなどはこのフレームワークの持ち物
https://jestjs.io/docs/ja/getting-started

テストの書き方、流れ


...
// テスト対象のコンポーネントをimoprt
import HogeComponent from '.';

...

beforeEach(async () => {
  // テストの前に実施する共通処理を定義
}

...

describe('テスト対象や条件を書く', () => {
  it('テストの内容を書く。xxが表示されること。yyが更新されること。など', async () => {
    // HogeComponentをレンダリング
    render(<HogeComponent />);

    // waitForを使ってレンダリングされるのを待つ
    await waitFor(() => {
      // Hogeというtitleが見つかるまで待機
      screen.getByTitle('Hoge');
    });

    // expectを使って要素をテスト
    expect(screen.getByText('hoge')).toEqual('hoge')
  });

  describe('さらに条件分岐する場合ネストさせる', () => {
    it('yyが更新されること', async () => {
      // HogeComponentをレンダリング
      render(<HogeComponent />);

      // userEventを使ってイベント発火
      userEvent.click(screen.getByRole('button'));

      // クリックイベントが終わるのを待つ
      await waitFor(() => {
        // Hogeというtitleが見つかるまで待機
        screen.getByTitle('Hoge');
      });

      // expectを使って要素をテスト
      expect(screen.getByText('hoge')).toEqual('hoge')
  });
});

React Testing Library

Reactをテストする時に役立つライブラリ群
https://testing-library.com/docs/react-testing-library/intro/

要素を取得する

getByHogeは該当する要素が見つからないとエラーになる。複数見つかった場合もエラーになるので注意。
queryByHogeは要素がないとnullが返却される。複数ある場合は先頭が返却される。
使うときの優先度や詳しい説明は下記を参照
https://testing-library.com/docs/queries/about

特定のボタンを取得する

// role='button', aria-label='hoge'のボタンを取得
screen.getByRole('button', { name: 'hoge' })

取得した要素から親のタグを検索

// テキストが'hoge'の要素の親の<tr>を探す
screen.getByText('hoge').closest('tr')

Roleについて

各タグにデフォルトで設定されているRoleは下記を参照
https://www.w3.org/TR/html-aria/

render

名前の通り、テスト要素をレンダリングする。

render(<Hoge>);

screen

レンダリングした要素にアクセスする

// 画面上のボタン要素を取得
screen.getByRole('button')

waitFor

初期表示や再描画されるのをまったりする時に使う

// 画面上にhogeが表示されるまで待つ
await waitFor(() => {
  screen.getByText(/hoge/);
});

within

画面上の一部にフォーカスする時に使う

// hogeが記載されている<tr>を取得してその中のbuttonを取得
const tr = screen.getByText('hoge').closest('tr');
const button = within(tr!).getByRole('button');

イベント発火

userEventを使う。
ボタンクリックやテキストの編集などができる。
https://testing-library.com/docs/ecosystem-user-event/

type

テキストの編集ができる。すでに値がある場合は追記になる。消したい場合はclearを使う。

userEvent.type(input, 'hoge');
// 入力後にフォーカスを外したい場合はtabを呼び出す
userEvent.tab();

Apollo

サーバーをモックする
https://www.apollographql.com/docs/react/development-testing/testing/

mocksは名前の通り複数渡すことができる。
queryとvariablesの組み合わせが一致するものが定義されていないとエラーが発生する。

import { MockedProvider } from '@apollo/client/testing';

const mocks = [
  {
    request: {
      query: HogeDocument,
      variables: {},
    },
    result: {
      data: {
        hoge: {
          id: 1,
          text: 'hoge'
        },
      },
    },
  },
  {
    request: {
      query: UpdateHogeDocument,
      variables: { id: 1, text: 'hoge' }
    },
    result: {
      data: {
        id: 1,
        text: 'hoge'
      }
    },
  }
];

it('hoge', async () => {
  render(
    <MockedProvider mocks={mocks} addTypename={false}>
      <Hoge />
    </MockedProvider>
  );

  // レンダー時に呼び出されるものはwaitForで待って確認

  // Mutationは発火させるアクションを実施したのちにwaitForで待って確認
});

mocks内のクエリーは1回のテストで1回しか利用できないようだ。
例えば上記の例でHogeDocumentを2回呼ぶと2回目はエラーになる。
2回呼ぶ場合はHogeDocumentをmocksに2つ定義しておく。
この仕様のおかげで呼び出しごとにレスポンスを変えることができる。