React Testing Libraryは、Reactコンポーネントの動作をユーザー視点でテストするための人気ライブラリです。その中でも、queryBy
、getBy
、findBy
は頻繁に利用されるクエリメソッドで、それぞれが異なる状況に特化した使い方を提供します。本記事では、これら3つのクエリの特徴や違いを解説し、どのような場面でどのクエリを使用すべきかを具体例を交えて説明します。テストの効率を向上させる知識を身に付け、Reactアプリケーション開発の品質をさらに高めましょう。
React Testing Libraryとは
React Testing Libraryは、Reactコンポーネントのテストに特化した軽量なライブラリで、ユーザーの操作をシミュレーションしながら、実際のアプリケーションの動作を検証することを目的としています。従来のテスト手法では、DOM要素や内部実装に依存することが多く、これがテストの保守性を低下させる要因となっていました。
React Testing Libraryの特徴
React Testing Libraryの主な特徴は以下の通りです:
- ユーザー視点:アプリケーションをユーザーが操作するようにテストを設計することで、リアルな動作検証を実現します。
- DOMに焦点:テスト対象のDOM構造に依存せず、アクセシブルな要素を操作することで、堅牢なテストを可能にします。
- 軽量設計:必要最小限の機能に絞り込むことで、簡潔かつ直感的にテストを書くことができます。
基本的なテストの考え方
React Testing Libraryでは、要素の選択や操作をDOM APIに準じたメソッドで行います。主要なクエリには以下が含まれます:
queryBy
系:要素が存在しないことを確認するために使用します。getBy
系:特定の要素を確実に取得する際に使用します。findBy
系:非同期処理によって動的に生成される要素を検証します。
これらのクエリを適切に使い分けることで、効率的かつ可読性の高いテストコードを実現できます。
queryBy, getBy, findByの基本概念
React Testing LibraryのクエリであるqueryBy
、getBy
、findBy
は、それぞれ異なる目的やシナリオに対応するために設計されています。このセクションでは、それぞれの基本的な概念と動作を解説します。
queryBy
queryBy
は、指定した条件に合致する要素をDOMから探し出し、それが存在する場合に要素を返します。一方で、要素が見つからない場合はnull
を返すため、エラーは発生しません。
主な用途
- 要素が存在しないことを確認するテスト(ネガティブテスト)。
- DOM操作の結果、要素が削除されているかどうかを検証する。
getBy
getBy
は、条件に合致する要素を確実に取得するためのクエリです。要素が見つからない場合は例外をスローします。このため、要素が必ず存在していると期待される場合に使用します。
主な用途
- DOM上に必ず存在すべき要素を取得して検証する。
- フォーム入力欄やボタンなど、操作対象となる要素の存在確認。
findBy
findBy
は、非同期に要素がレンダリングされる場合に使用されます。Promiseを返すため、要素が一定時間内に見つかるかどうかを検証するのに適しています。
主な用途
- 非同期処理によって生成される要素をテストする。
- API呼び出し後に表示されるデータやエラーメッセージの確認。
クエリの違いを理解する重要性
これらのクエリを適切に使い分けることで、テストの意図を明確にし、不要なエラーを避けつつ、効率的なテストコードを構築できます。次のセクションでは、これらのクエリの返り値やエラーハンドリングの違いについてさらに詳しく解説します。
クエリの返り値とエラーハンドリングの違い
React Testing LibraryのクエリであるqueryBy
、getBy
、findBy
は、動作や失敗時の挙動が異なります。このセクションでは、それぞれのクエリが返す値とエラーハンドリングの違いについて詳しく説明します。
queryByの返り値とエラーハンドリング
- 返り値:条件に合致する要素が見つかれば、その要素を返します。見つからない場合は
null
を返します。 - エラーハンドリング:要素が見つからなくてもエラーはスローされません。このため、要素が存在しないことを確認するネガティブテストに適しています。
例
const nonExistentElement = screen.queryByText('存在しない要素');
expect(nonExistentElement).toBeNull(); // 要素がないことを確認
getByの返り値とエラーハンドリング
- 返り値:条件に合致する単一の要素を返します。複数の要素が見つかる場合は例外がスローされます。
- エラーハンドリング:条件に合致する要素が見つからない場合は、
TestingLibraryElementError
がスローされます。このため、要素が必ず存在することを前提としたテストに適しています。
例
const element = screen.getByText('特定の要素');
expect(element).toBeInTheDocument(); // 要素が存在することを確認
findByの返り値とエラーハンドリング
- 返り値:Promiseを返します。条件に合致する要素が見つかれば、その要素を返すPromiseが解決されます。
- エラーハンドリング:要素が見つからない場合、指定されたタイムアウト時間後にPromiseが拒否され、エラーがスローされます。非同期の要素レンダリングをテストする際に適しています。
例
const asyncElement = await screen.findByText('非同期で生成される要素');
expect(asyncElement).toBeInTheDocument(); // 非同期要素の存在を確認
クエリ選択のポイント
queryBy
:要素が存在しないことを確認する際に使用。getBy
:要素が確実に存在する前提でテストする場合に使用。findBy
:非同期に要素が生成される状況で使用。
クエリの返り値とエラーハンドリングを理解することで、意図した動作を正確にテストし、効率的なテストコードを構築できます。次のセクションでは、これらのクエリの選択と使用場面について具体例を交えて解説します。
適切なクエリの選び方と使用場面
React Testing LibraryでqueryBy
、getBy
、findBy
を使い分けるためには、それぞれの特性を理解した上で、テストの目的に応じた選択をする必要があります。このセクションでは、シナリオごとに適切なクエリを選ぶポイントを解説します。
queryByの使用場面
queryBy
は、要素が存在しないことを確認したい場合に使用します。これは特に、条件によって要素が削除されたり、初期状態で表示されないことを検証する際に役立ちます。
使用例
- 削除ボタンを押した後に、削除対象の要素がDOMから消えているか確認する場合。
- 非表示状態の要素が描画されていないことを検証する場合。
fireEvent.click(screen.getByText('削除ボタン'));
expect(screen.queryByText('削除対象')).toBeNull();
getByの使用場面
getBy
は、要素が確実に存在している場合に使用します。このクエリは要素が見つからないとエラーをスローするため、要素が必ず存在することを前提にしたテストに適しています。
使用例
- 初期状態で表示されるボタンやラベルなどを確認する場合。
- 必ず存在する入力フィールドに値を入力する場合。
const input = screen.getByPlaceholderText('名前を入力してください');
fireEvent.change(input, { target: { value: 'テストユーザー' } });
expect(input.value).toBe('テストユーザー');
findByの使用場面
findBy
は、非同期処理で要素が動的に生成される場合に使用します。例えば、API呼び出しの後に表示されるデータや、遅延してレンダリングされる要素を検証する際に適しています。
使用例
- ローディング表示の後に、非同期で表示されるデータを検証する場合。
- フォーム送信後に表示されるエラーメッセージを確認する場合。
await waitFor(() => expect(screen.queryByText('ローディング中')).toBeNull());
const asyncData = await screen.findByText('APIから取得したデータ');
expect(asyncData).toBeInTheDocument();
クエリ選択のベストプラクティス
- DOM操作後に要素が消えているかを確認する:
queryBy
を使用。 - 要素が必ず存在することを前提にする:
getBy
を使用。 - 非同期要素のテスト:
findBy
を使用。
これらの選択基準を理解することで、意図したテストケースを正確にカバーし、無駄のないテストコードを作成することが可能です。次のセクションでは、各クエリを使用した具体的なテストシナリオをさらに詳しく見ていきます。
実践例:queryByを使ったエラー処理テスト
queryBy
は、要素がDOMに存在しないことを確認するテストに特化したクエリです。このセクションでは、queryBy
を活用してエラー処理や削除機能を検証する具体的なテストシナリオを紹介します。
ケーススタディ:エラー通知が非表示になるかの確認
エラー通知が表示された後、一定時間経過すると非表示になるアプリケーションの動作をテストする例を考えます。
対象コンポーネントのコード例
import React, { useState } from 'react';
function ErrorMessage({ message }) {
const [visible, setVisible] = useState(true);
React.useEffect(() => {
const timer = setTimeout(() => setVisible(false), 3000);
return () => clearTimeout(timer);
}, []);
if (!visible) return null;
return <div role="alert">{message}</div>;
}
export default ErrorMessage;
テストコード例
このコンポーネントが正しく動作するかをqueryBy
を使って検証します。
import { render, screen } from '@testing-library/react';
import ErrorMessage from './ErrorMessage';
test('エラー通知が一定時間後に非表示になる', async () => {
render(<ErrorMessage message="エラーが発生しました" />);
// 初期状態ではエラー通知が表示されていることを確認
const errorElement = screen.queryByRole('alert');
expect(errorElement).toBeInTheDocument();
expect(errorElement).toHaveTextContent('エラーが発生しました');
// 3秒後にはエラー通知が非表示になっていることを確認
await new Promise((r) => setTimeout(r, 3000));
expect(screen.queryByRole('alert')).toBeNull();
});
ポイント解説
- 初期状態の確認:
queryByRole
を使用してエラー通知がDOMに存在することを検証します。 - 非表示状態の確認:3秒後に再度
queryByRole
を使い、エラー通知がDOMに存在しないことを確認します。null
が返されることを期待します。
応用:条件による表示切り替えのテスト
条件によって要素の表示・非表示が切り替わるケースでもqueryBy
は有効です。以下のような削除機能の確認にも使用できます。
fireEvent.click(screen.getByText('削除ボタン'));
expect(screen.queryByText('削除された要素')).toBeNull();
まとめ
queryBy
は、要素の非表示や削除状態を確認するテストに不可欠なツールです。要素が存在しないことを正確に検証することで、UIやアプリケーションロジックの不具合を防ぐことができます。次のセクションでは、getBy
を使ったテストシナリオを詳しく解説します。
実践例:getByで確実に要素を取得するテスト
getBy
は、DOM内に必ず存在する要素を取得するために使用されるクエリです。要素が見つからない場合にはエラーをスローするため、要素の存在を前提としたテストケースに最適です。このセクションでは、getBy
を活用した具体的なテストシナリオを紹介します。
ケーススタディ:ログインフォームの存在確認
ログインフォームが正しく表示され、ユーザーが利用できる状態であるかを検証するテストを作成します。
対象コンポーネントのコード例
function LoginForm() {
return (
<form>
<label htmlFor="username">ユーザー名</label>
<input id="username" placeholder="ユーザー名を入力" />
<label htmlFor="password">パスワード</label>
<input id="password" type="password" placeholder="パスワードを入力" />
<button type="submit">ログイン</button>
</form>
);
}
export default LoginForm;
テストコード例
このフォームが正しくレンダリングされるかをgetBy
を使用してテストします。
import { render, screen } from '@testing-library/react';
import LoginForm from './LoginForm';
test('ログインフォームが正しく表示される', () => {
render(<LoginForm />);
// ユーザー名入力フィールドを取得
const usernameInput = screen.getByPlaceholderText('ユーザー名を入力');
expect(usernameInput).toBeInTheDocument();
expect(usernameInput).toHaveAttribute('id', 'username');
// パスワード入力フィールドを取得
const passwordInput = screen.getByPlaceholderText('パスワードを入力');
expect(passwordInput).toBeInTheDocument();
expect(passwordInput).toHaveAttribute('id', 'password');
// ログインボタンを取得
const loginButton = screen.getByText('ログイン');
expect(loginButton).toBeInTheDocument();
});
ポイント解説
- 正確な要素取得:
getByPlaceholderText
やgetByText
を用いて、確実に要素を取得しています。 - 属性検証:取得した要素が正しい属性(例:
id
)を持つか確認しています。 - 表示確認:要素がDOMに存在することを
toBeInTheDocument
で検証しています。
応用:複数の要素が含まれる場合のテスト
getBy
系クエリは、複数要素が条件に合致する場合エラーをスローします。この特性を利用して、DOMの意図しない状態を検出できます。
test('ユーザー名フィールドは1つのみ存在する', () => {
render(<LoginForm />);
expect(() => screen.getAllByPlaceholderText('ユーザー名を入力')).toThrow();
});
まとめ
getBy
を利用することで、要素の存在を前提とした確実なテストを行えます。フォームや重要なUI要素の検証に活用することで、DOMの状態を正確に把握し、テストの堅牢性を向上させましょう。次のセクションでは、非同期要素を対象としたfindBy
のテスト手法について解説します。
実践例:findByによる非同期要素のテスト
findBy
は、非同期処理によって動的に生成される要素を検証する際に使用されるクエリです。Promiseを返すため、非同期処理の完了を待ってから要素の存在を確認できます。このセクションでは、findBy
を活用した具体的な非同期要素のテスト例を紹介します。
ケーススタディ:APIデータのレンダリングテスト
API呼び出し後に、取得したデータが表示されるアプリケーションをテストする例を考えます。
対象コンポーネントのコード例
import React, { useState, useEffect } from 'react';
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
setTimeout(() => {
setUsers(['Alice', 'Bob', 'Charlie']);
setLoading(false);
}, 2000);
}, []);
if (loading) return <div>ローディング中...</div>;
return (
<ul>
{users.map((user) => (
<li key={user}>{user}</li>
))}
</ul>
);
}
export default UserList;
テストコード例
findBy
を使用して、APIから取得したユーザーデータが正しく表示されるかをテストします。
import { render, screen } from '@testing-library/react';
import UserList from './UserList';
test('ユーザーリストが非同期で表示される', async () => {
render(<UserList />);
// 初期状態ではローディングメッセージが表示されることを確認
expect(screen.getByText('ローディング中...')).toBeInTheDocument();
// 非同期処理完了後にユーザーリストが表示されることを確認
const userElement = await screen.findByText('Alice');
expect(userElement).toBeInTheDocument();
// ローディングメッセージが非表示になることを確認
expect(screen.queryByText('ローディング中...')).toBeNull();
});
ポイント解説
- 非同期動作の確認:
findByText
を使用して、Promiseが解決された後に特定の要素が表示されることを検証しています。 - 初期状態の確認:
getByText
を使って、非同期処理が実行される前の状態を確認しています。 - 要素の非表示確認:
queryByText
を併用し、ローディングメッセージが非表示になることを検証しています。
応用:複数要素の非同期レンダリング確認
リスト全体が正しく表示されているかをテストする場合には、findAllBy
を使用できます。
const userElements = await screen.findAllByRole('listitem');
expect(userElements).toHaveLength(3);
expect(userElements.map((el) => el.textContent)).toEqual(['Alice', 'Bob', 'Charlie']);
まとめ
findBy
は、非同期処理によって生成される要素を正確に検証するための強力なツールです。ローディング状態やデータの動的レンダリングを伴うコンポーネントのテストに不可欠です。次のセクションでは、複数のクエリを組み合わせたテストテクニックについて解説します。
クエリの組み合わせテクニック
React Testing Libraryでは、queryBy
、getBy
、findBy
を単独で使用するだけでなく、複数のクエリを組み合わせることで、より柔軟で効率的なテストを実現できます。このセクションでは、クエリを組み合わせたテストテクニックを具体例とともに解説します。
ケーススタディ:動的な要素表示の状態を段階的にテストする
ボタンをクリックすると非同期でリストが表示されるコンポーネントを想定し、そのテストを段階的に検証します。
対象コンポーネントのコード例
import React, { useState } from 'react';
function DynamicList() {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(false);
const fetchItems = () => {
setLoading(true);
setTimeout(() => {
setItems(['Item 1', 'Item 2', 'Item 3']);
setLoading(false);
}, 2000);
};
return (
<div>
<button onClick={fetchItems}>データを取得</button>
{loading && <div>ローディング中...</div>}
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}
export default DynamicList;
テストコード例
以下では、クエリを組み合わせて、動的に表示される状態を検証します。
import { render, screen, fireEvent } from '@testing-library/react';
import DynamicList from './DynamicList';
test('ボタンをクリック後にリストが表示される', async () => {
render(<DynamicList />);
// 初期状態でリストは空
expect(screen.queryByRole('listitem')).toBeNull();
// ボタンをクリックしてデータを取得
fireEvent.click(screen.getByText('データを取得'));
// ローディング中の表示を確認
expect(screen.getByText('ローディング中...')).toBeInTheDocument();
// 非同期処理完了後、リストが表示されることを確認
const listItems = await screen.findAllByRole('listitem');
expect(listItems).toHaveLength(3);
expect(listItems.map((item) => item.textContent)).toEqual(['Item 1', 'Item 2', 'Item 3']);
// ローディング中の表示が消えていることを確認
expect(screen.queryByText('ローディング中...')).toBeNull();
});
テクニック解説
queryBy
で初期状態を確認:要素が表示されていないことを確認します。getBy
でアクションをトリガー:ボタンのクリック操作を実行します。findBy
で非同期要素を確認:Promiseを使用して、非同期でレンダリングされる要素を検証します。- 組み合わせた状態確認:
queryBy
で不要な要素が削除されていることもチェックします。
応用:条件分岐による動的要素の検証
条件によって異なる要素が表示される場合も、クエリを組み合わせることで検証可能です。
if (condition) {
expect(screen.queryByText('条件Aのメッセージ')).toBeInTheDocument();
} else {
expect(screen.queryByText('条件Bのメッセージ')).toBeNull();
}
まとめ
クエリの組み合わせにより、複雑な動的UIを効率的にテストできます。それぞれのクエリの特性を活かし、柔軟なテストケースを構築することで、アプリケーションの品質を向上させることができます。次のセクションでは、カスタムクエリの作成方法について解説します。
応用:カスタムクエリの作成と使用例
React Testing Libraryでは、標準のクエリだけでなく、独自のカスタムクエリを作成してテストに活用できます。特定の要素や属性に基づいたカスタムクエリを定義することで、独自仕様のアプリケーションに対応した柔軟なテストが可能になります。このセクションでは、カスタムクエリの作成方法とその活用例を紹介します。
ケーススタディ:カスタムデータ属性で要素を取得する
アプリケーション内で、特定のデータ属性(例:data-testid
)を使用して要素を識別する場合に、カスタムクエリを作成して効率的に要素を取得します。
カスタムクエリの作成方法
React Testing Libraryでは、configure
関数を使用してカスタムクエリを追加できます。
import { configure, queries, screen } from '@testing-library/react';
const customQueries = {
getByTestIdCustom: (container, id) =>
queries.queryByAttribute('data-testid', container, id),
};
configure({
queries: { ...queries, ...customQueries },
});
上記の例では、data-testid
属性を基準に要素を取得するgetByTestIdCustom
を定義しています。
カスタムクエリを使用したテスト例
定義したカスタムクエリを使い、特定の要素が表示されるかをテストします。
function TestComponent() {
return <div data-testid="custom-element">カスタム要素</div>;
}
test('カスタムクエリで要素を取得する', () => {
render(<TestComponent />);
// カスタムクエリを使用して要素を取得
const element = screen.getByTestIdCustom('custom-element');
expect(element).toBeInTheDocument();
expect(element.textContent).toBe('カスタム要素');
});
カスタムクエリの応用例
カスタムクエリは、より複雑な条件に基づいて要素を取得する場合にも活用できます。例えば、特定のクラス名や独自の属性で要素を絞り込む場合です。
const customQueries = {
getByCustomClass: (container, className) =>
Array.from(container.querySelectorAll(`.${className}`))[0],
};
このクエリを使えば、特定のクラスを持つ要素を効率的に取得可能です。
ポイント解説
- 再利用性の向上:カスタムクエリを用いることで、共通パターンを簡潔にテストできます。
- 柔軟性:標準クエリでは対応しづらい独自仕様にも対応可能です。
- シンプルなコード:テストコードの可読性を高めます。
まとめ
カスタムクエリを作成することで、テストの柔軟性と効率が向上します。独自仕様や複雑な条件を持つアプリケーションでも、明確で直感的なテストを実現できるため、プロジェクト全体の品質保証に大いに役立ちます。最後に、今回の記事を通じて紹介したクエリの使い分けを振り返り、効率的なテストコードの書き方を確認しましょう。
まとめ
本記事では、React Testing Libraryの主要クエリであるqueryBy
、getBy
、findBy
について、それぞれの特徴や使い分け、具体的な活用例を詳しく解説しました。
queryBy
:要素が存在しないことを確認するネガティブテストに最適。getBy
:要素が必ず存在することを前提としたテストに活用。findBy
:非同期で要素が生成されるシナリオに適している。
さらに、クエリを組み合わせた高度なテスト手法や、独自仕様に対応するためのカスタムクエリの作成方法も紹介しました。これらの知識を活用することで、テストコードの効率と可読性を大幅に向上させることができます。
適切なクエリの選択と設計は、Reactアプリケーションの品質保証において重要な鍵となります。本記事を参考に、柔軟で堅牢なテストを構築して、開発の生産性をさらに向上させてください。
コメント