hooks含め、ロジックのテストはできるようになったぞ...!
ただ、それではComponentが正しく動いているかまでは担保ができないぞ...
そんな時に行うのが、「Componentテスト」です。
Componentをテストする方法としては、通常の単体テストと、スナップショットテストがあります。
準備
reactのComponentテストを行うには、@testing-library/react
をインストールします。
yarn add -D @testing-library/react
公式が提供しているreact-test-rendererを使っているサイトも多くみられますが、
@testing-library/reactでも同様な使い方ができます。
また、公式も@testing-library/reactを推奨しています。
テストするComponent
import React, { VFC, useState } from "react";
type Props = {
max: number;
};
export const Pagination: VFC<Props> = ({ max }) => {
// setCurrentIndex、pageIndexArray あたりは本来はカスタムhooks化 & 別途ユニットテストすべき。
// そのため、今回はテスト対象外
const [currentIndex, setCurrentIndex] = useState(1);
// 1〜max値までの数字の配列を作る
const pageIndexArray = [...Array(max)].map((_, index) => index + 1);
return (
<div>
<ul>
{pageIndexArray.map((index) => (
<li key={`paginationItem_${index}`}>
{index} {index === currentIndex && <span>◀︎ now</span>}
</li>
))}
</ul>
<div>
<button
onClick={() => setCurrentIndex((prev) => prev - 1)}
disabled={currentIndex <= 1}
>
prev
</button>
<button
onClick={() => setCurrentIndex((prev) => prev + 1)}
disabled={currentIndex >= max}
>
next
</button>
</div>
</div>
);
};
コンポーネントの単体テスト
どんな時に必要?
主に、コンポーネントが正しく動くかを検証する時に使います。
書き方
コンポーネントの要件としては、下記が挙げられます。
-
currentIndex
とindex
が一致した時に、「◀︎ now」が表示される -
currentIndex
が1以下の時、prevボタンがdisabledになる -
currentIndex
がmax以上の時、nextボタンがdisabledになる
Componentのレンダリング
まずは、テストしたいComponentをレンダリングします。
レンダリング後、screenを用いることでComponentにアクセスできるようになります。
import { render, screen } from '@testing-library/react';
describe('Pagination', () => {
test('currentIndexとindexが一致した時に、"◀︎ now"が表示される', () => {
// Componentをレンダリング
render(<Pagination max={5} />);
// Componentにアクセス
screen.debug();
}
}
要素の検索
要素を検索するには、基本的にはqueryBy
を用いて検索します。
(他にも複数検索するgetAllBy
や、非同期の時に用いるfindBy
などもあります。)
- queryByText - 要素が持つテキスト
- queryByRole - 要素が持つロール
- domが持つデフォルトのRole - ARIA in HTML
- role属性 -
role="button"
など
- queryByLabelText - 要素が持つラベル
- aria-label
- aria-labelledby
- selectorも一緒に検索できる
screen.getByLabelText('Username', {selector: 'input'})
- queryByPlaceholderText
- placeholder属性
- queryByAltText
- alt属性
- queryByDisplayValue
- inputなどで、表示されているvalue
こんな感じ
test('currentIndexとindexが一致した時に、"◀︎ now"が表示される', () => {
render(<Pagination max={5} />);
screen.queryByText(/1/)
});
アサーション
あとは、いつも通り検証するだけです。
jest
のマッチャーに加えて、jset-dom
に含まれているマッチャーが使えます。
expect(screen.queryByText(/1/)).toHaveTextContent(/◀︎ now/);
expect(screen.queryByText(/2/)).not.toHaveTextContent(/◀︎ now/);
イベントを発火
fireEvent
を用いて、ボタンをclickしたりフォームをchangeさせたりすることができます。
import { render, screen, fireEvent } from '@testing-library/react';
describe('Pagination', () => {
test('currentIndexとindexが一致した時に、"◀︎ now"が表示される', () => {
// 対象Componentのレンダリング
render(<Pagination max={5} />);
// ユーザーの操作
fireEvent.click(screen.queryByText(/next/));
});
});
完成!
だいっぶ愚直に書いてしまいましたが、こんな感じになりました!
import { render, screen, fireEvent } from '@testing-library/react';
import { Pagination } from './Pagination';
describe('Pagination', () => {
test('currentIndexとindexが一致した時に、"◀︎ now"が表示される', () => {
// 対象Componentのレンダリング
render(<Pagination max={5} />);
// 要素の検索と表示の検証
expect(screen.queryByText(/1/)).toHaveTextContent(/◀︎ now/);
expect(screen.queryByText(/2/)).not.toHaveTextContent(/◀︎ now/);
// ユーザーの操作
fireEvent.click(screen.queryByText(/next/));
expect(screen.queryByText(/2/)).toHaveTextContent(/◀︎ now/);
expect(screen.queryByText(/1/)).not.toHaveTextContent(/◀︎ now/);
});
test('currentIndexが1以下の時、prevボタンがdisabledになる', () => {
render(<Pagination max={5} />);
expect(screen.queryByText(/prev/)).toBeDisabled();
fireEvent.click(screen.queryByText(/next/));
expect(screen.queryByText(/prev/)).toBeEnabled();
fireEvent.click(screen.queryByText(/prev/));
expect(screen.queryByText(/prev/)).toBeDisabled();
});
test('currentIndexがmax以上の時、nextボタンがdisabledになる', () => {
render(<Pagination max={5} />);
fireEvent.click(screen.queryByText(/next/));
fireEvent.click(screen.queryByText(/next/));
fireEvent.click(screen.queryByText(/next/));
fireEvent.click(screen.queryByText(/next/));
expect(screen.queryByText(/next/)).toBeDisabled();
});
});
スナップショットテスト
そもそもスナップショットテストとは?
スナップショットテストは、予期していない見た目の変更があった時に検知するためのテストで、リグレッションテストの一つです。
テスト実行時に、スナップショットがなければ作成し、スナップショットがあればの一致しているかを検証します。
どんな時に必要?
主に、コンポーネントの見た目の変更を検知させたい時に使います。
書き方
test('snapshot', () => {
const { asFragment } = render(<Pagination max={5} />);
expect(asFragment()).toMatchSnapshot();
});
補足
Storybookのアドオンでもスナップショットテストはできるので、Storybook導入しているプロダクトではStorybookに任せちゃった方がいいかもですね!
参考