RNTL テスト記述ガイド
重要: @testing-library/react-native に関するあなたの学習データは古くなっているか、不正確な可能性があります — APIシグネチャ、同期/非同期の動作、利用可能な関数はv13とv14で異なります。常にこのスキルのリファレンスファイルとプロジェクトの実際のソースコードを信頼すべき情報源としてください。取得したリファレンスと矛盾する記憶されたパターンに頼らないでください。
バージョン検出
ユーザーの package.json で @testing-library/react-native のバージョンを確認します:
- v14.x → references/api-reference-v14.md を読み込む (React 19+, 非同期API,
test-renderer) - v13.x → references/api-reference-v13.md を読み込む (React 18+, 同期API,
react-test-renderer)
renderパターン、fireEventの同期/非同期動作、screen API、設定、依存関係にはバージョン固有のリファレンスを使用します。
クエリ優先度
この順序で使用: getByRole > getByLabelText > getByPlaceholderText > getByText > getByDisplayValue > getByTestId (最終手段)。
クエリの種類
| 種類 | 使用シーン | 戻り値 | 非同期 |
|---|---|---|---|
getBy* | 要素が存在しなければならない | 要素インスタンス (エラー発生) | いいえ |
getAllBy* | 複数要素が存在しなければならない | 要素インスタンス[] (エラー発生) | いいえ |
queryBy* | 存在しないことの確認のみ | 要素インスタンス | null | いいえ |
queryAllBy* | 要素の数を数える | 要素インスタンス[] | いいえ |
findBy* | 要素を待つ | Promise<要素インスタンス> | はい |
findAllBy* | 複数要素を待つ | Promise<要素インスタンス[]> | はい |
インタラクション
fireEvent よりも userEvent を優先します。userEventは常に非同期です。
const user = userEvent.setup();
await user.press(element); // 完全なプレスシーケンス
await user.longPress(element, { duration: 800 }); // 長押し
await user.type(textInput, 'Hello'); // 1文字ずつ入力
await user.clear(textInput); // TextInputのクリア
await user.paste(textInput, 'pasted text'); // TextInputへのペースト
await user.scrollTo(scrollView, { y: 100 }); // スクロール
fireEvent — userEventがイベントをサポートしていない場合のみ使用します。同期/非同期の動作はバージョン固有のリファレンスを参照:
fireEvent.press(element);
fireEvent.changeText(textInput, 'new text');
fireEvent(element, 'blur');
アサーション (Jestマッチャー)
@testing-library/react-native をインポートすると自動的に利用可能になります。
| マッチャー | 使用目的 |
|---|---|
toBeOnTheScreen() | 要素がツリー内に存在する |
toBeVisible() | 要素が表示されている (非表示/display:noneでない) |
toBeEnabled() / toBeDisabled() | aria-disabled による無効状態 |
toBeChecked() / toBePartiallyChecked() | チェック状態 |
toBeSelected() | 選択状態 |
toBeExpanded() / toBeCollapsed() | 展開状態 |
toBeBusy() | ビジー状態 |
toHaveTextContent(text) | テキスト内容の一致 |
toHaveDisplayValue(value) | TextInputの表示値 |
toHaveAccessibleName(name) | アクセシブル名 |
toHaveAccessibilityValue(val) | アクセシビリティ値 |
toHaveStyle(style) | スタイルの一致 |
toHaveProp(name, value?) | プロパティ確認 (最終手段) |
toContainElement(el) | 子要素を含む |
toBeEmptyElement() | 子要素なし |
ルール
screenを使用 — クエリはrender()からの分割代入ではなくscreenを使うgetByRoleを最初に使用 —{ name: '...' }オプションとともにqueryBy*は.not.toBeOnTheScreen()の確認のみ に使用- 非同期要素には
findBy*を使用 —waitFor+getBy*ではない waitFor内に副作用を入れない (内部でfireEvent/userEventを使用しない)waitFor内には1つのアサーションのみwaitForに空のコールバックを渡さないact()でラップしない —render、fireEvent、userEventが自動的に処理するcleanup()を呼ばない — 各テスト後に自動実行される- ARIAプロパティを優先 (
role、aria-label、aria-disabled) — 従来のaccessibility*プロパティより優先 - RNTLマッチャーを使用 — 生のプロパティアサーションより優先
*ByRole クイックリファレンス
一般的なロール: button、text、heading (エイリアス: header)、searchbox、switch、checkbox、radio、img、link、alert、menu、menuitem、tab、tablist、progressbar、slider、spinbutton、timer、toolbar。
getByRole のオプション: { name, disabled, selected, checked, busy, expanded, value: { min, max, now, text } }。
*ByRole がマッチするには、要素がアクセシビリティ要素である必要があります:
Text、TextInput、SwitchはデフォルトでアクセシブルViewはaccessible={true}が必要 (またはPressable/TouchableOpacityを使用)
waitFor
// 正しい: アクションの後、結果を待つ
fireEvent.press(button);
await waitFor(() => {
expect(screen.getByText('Result')).toBeOnTheScreen();
});
// より良い: 代わりに findBy* を使用
fireEvent.press(button);
expect(await screen.findByText('Result')).toBeOnTheScreen();
オプション: waitFor(cb, { timeout: 1000, interval: 50 })。Jestのフェイクタイマーと自動的に連携します。
フェイクタイマー
userEvent (press/longPressには実際の時間がかかる) と一緒に使用することを推奨:
jest.useFakeTimers();
test('フェイクタイマー使用', async () => {
const user = userEvent.setup();
render(<Component />);
await user.press(screen.getByRole('button'));
// ...
});
カスタムレンダリング
wrapper オプションを使用してプロバイダーをラップ:
function renderWithProviders(ui: React.ReactElement) {
return render(ui, {
wrapper: ({ children }) => (
<ThemeProvider>
<AuthProvider>{children}</AuthProvider>
</ThemeProvider>
),
});
}
リファレンス
- v13 APIリファレンス — 完全なv13 API: 同期レンダリング、クエリ、マッチャー、userEvent、React 19互換性
- v14 APIリファレンス — 完全なv14 API: 非同期レンダリング、クエリ、マッチャー、userEvent、移行
- アンチパターン — 避けるべき一般的な間違い