メインコンテンツへスキップ

React Native Testing Libraryでアプリを正しくテストする方法

React Native Testing Libraryのセットアップ、クエリ、モック、CIのヒントを習得。コンポーネント、フック、ナビゲーション向けの、ユーザー中心で信頼できるテストを構築しましょう。

React Native Testing Libraryでアプリを正しくテストする方法

React Nativeのテストスイートはグリーンなのに、実機で「Continue」をタップしても何も起こらないというユーザー報告がまだある。コンポーネントテストはボタンを見つけ、ハンドラーを呼び出し、モックされたJavaScript環境で期待どおりの画面を確認した。しかし、ネイティブの権限プロンプト、キーボードの挙動、アニメーション、プラットフォームのAPI、実際のナビゲーションスタックは検証していなかった。

そのギャップこそが、チームに誤った安心感を与える原因です。 React Native Testing Library コンポーネントのレンダリング内容やユーザー操作への反応をテストするには最適ですが、デバイステストやパフォーマンス計測の代わりにはなりません。信頼できる戦略は、各レイヤーが検出できる失敗に対して、そのレイヤーを活用することです。

目次

なぜユーザー中心のテストがすべてを変えるのか

よくあるテストの失敗は、テストを書く前から始まっています。開発者がコンポーネントのpropsを調べたり、stateにアクセスしたり、巨大なスナップショットと比較したりするのは、それらがアサーションしやすいからです。テストはパスしますが、リファクタリングでユーザー体験を変えずに構造だけ変更すると、テストスイートが壊れます。さらに悪いことに、アサーションがユーザーに見えるべき内容を記述していなければ、ユーザーから見える挙動が間違っていてもテストがパスし続けてしまうことがあります。

React Native Testing Libraryは、それとは逆のアプローチを取ります。コンポーネントをレンダリングし、目に見えるコントロールを通じて操作を行い、ユーザーが観察できる結果をアサーションします。このアプローチは、 React Native Testing Libraryのユーザー中心の例、およびReact Nativeのテストガイダンス(テストを短く保つ、各テストの焦点を1つにする、表示の関心事とビジネスロジック・stateを分離する、内部実装の詳細よりも目に見える出力やアクセシビリティヘルパーを優先する)と一致しています。

ユーザーの行動、変更への耐性、信頼性、ベストプラクティスを強調し、ユーザー中心テストの利点を示す図。

ユーザーが依存する挙動をテストする

例えば、ログインフォームがリクエスト実行中に送信ボタンを無効化するとします。壊れやすいテストでは、 disabled 特定のコンポーネントインスタンスに対して、または状態変数が変更されたことをアサートします。より強力なテストでは、アクセシブルな「Sign in」コントロールを押下し、ローディングインジケーターを待ってから、エラーメッセージや遷移先画面が表示されることを確認します。

2番目のテストは、フォームがローカル状態、reducer、カスタムフック、あるいは異なるボタンの実装を使用しているかどうかを気にしません。アプリが正しい結果を伝えているかどうかが重要です。

実践的なルール: ユーザーが観測できないものは、コンポーネントの動作テストに含めるべきかどうかを問い直してください。

アクセシビリティラベルやロールに基づくクエリは、より良いプロダクトのcodeを強制します。意味のあるラベルを公開する画面は、支援技術での利用が容易になり、テストでの検証もしやすくなります。より広い観点から評価する際、この関連性は重要です。 アプリのユーザー体験。テスト可能性と使いやすさは、しばしば同時に向上するからです。

テストがパスしても証明できないこと

ユーザー中心のコンポーネントテストは、JavaScriptが期待されるブランチを描画し、シミュレートされたタップに反応することを証明できます。しかし、生体認証プロンプトが正しく開くこと、ネイティブカメラが使用可能な結果を返すこと、または支払いフローがプラットフォーム固有のライフサイクル挙動に耐えることは証明できません。

この境界はライブラリの弱点ではありません。テストレイヤーの誠実さを保つ理由です。コンポーネントの挙動にはRNTLを使用し、ネイティブ統合、実際のナビゲーション、権限、認証、支払い、またはコアアプリ機能の結果を変えうるフローには、デバイスベースのテストを割り当ててください。

実際に使えるインストールと設定

モダンなセットアップは、スコープ付きパッケージから始まります:

@testing-library/react-native

以前の react-native-testing-library npm パッケージ名は歴史的な名残として存在していますが、現在のプロジェクトでは Testing Library ファミリー内でメンテナンスされているスコープ付きパッケージを使用すべきです。このプロジェクトの GitHub リポジトリ 優れたテストプラクティスを促進するための React Native ユーティリティであると説明されており、リリース履歴からも、単なる静的なヘルパーではなく React Native と共に進化し続けていることがわかります。

既存の Jest 環境にライブラリをインストールします。Expo プロジェクトでは通常 Expo Jest preset が使用され、bare React Native プロジェクトでは React Native preset が使用されます:

npm install --save-dev @testing-library/react-native jest

Expo の場合は、プロジェクトですでに使用している preset を追加してください:

{
  "scripts": {
    "test": "jest"
  },
  "jest": {
    "preset": "jest-expo"
  }
}

bare React Native アプリケーションの場合は、代わりに対応する React Native Jest 設定を使用してください。preset はフレームワークのバージョンに合わせてください。RNTL の問題に見えるエラーの多くは、React renderer、Babel transform、または Jest preset の不一致が原因です。

セットアップファイルは明示的に保つ

各テストで mock を繰り返すのではなく、共通の環境設定は setup ファイルにまとめましょう:

// jest.setup.js
import '@testing-library/react-native/extend-expect';

次に、Jest から参照します:

{
  "jest": {
    "preset": "jest-expo",
    "setupFilesAfterEnv": ["<rootDir>/jest.setup.js"]
  }
}

アプリで React Navigation、Reanimated、gesture handling、safe-area context、storage モジュールを使っている場合は、テスト環境に必要な mock だけを設定してください。アプリの動作を変えるグローバル mock は、すべてのテストを通りやすくする一方で、テストスイートの信頼性を下げます。

TypeScript にも同じ注意が必要です。Jest が .ts と .tsx preset または Babel 設定を通じてファイルを変換するようにし、コンパイラがテスト型を参照できるようにしてください。ローカルでは実行できても型チェックされないテストは、誤った query 名、無効な navigation パラメータ、安全でない mock の形状を見逃す可能性があります。

リリース履歴は、古いセットアップ手順を調べるときに役立ちます。プロジェクトには 127 件のリリースがあり、、さらに v14.0.1 は 2026-06-23 にタグ付けされています。。一方、 2024-11-27にリリースされたv12.9.0では、React Native 0.77およびExpo 52の公式サポートが追加されました。2025年3月のv14 alphaラインでは、非推奨となったReact Test RendererからUniversal Test Rendererへ移行し、React 19専用サポートへの準備が行われました。詳細は RNTLのリリース履歴そのため、アプリのバージョンを確認せずに古いチュートリアルからレンダラーの依存関係をコピーしないでください。

実践的なJestの基盤づくりには、設定をこちらと比較し、 JavaScriptユニットテストガイド次に、navigationやnativeのモックを追加する前に、小さなコンポーネントテストを1つ実行しましょう。

React Native Testing Libraryのプロジェクトをセットアップする5ステップのインストール手順を示すインフォグラフィック。

主要APIのQueryとAssertionを解説

RNTLのテストは、クエリがユーザーに見て、見つけて、操作できるものと一致すると信頼できるものになります。API 自体は小さいですが、実装の詳細を露出するセレクターを選ぶと、通っているテストが誤解を招く可能性があります。

まずは render:

const screen = render(<LoginForm />);

ユーザーにとって最も関連性の高いクエリを選びましょう。コンポーネントが公開している場合はアクセシビリティ指向のクエリを優先し、テキストが振る舞いそのものである場合は表示テキストを使い、次の場合にのみ取っておきましょう。 testID 意味のあるユーザー向けセレクターがない場合や、安定した統合フックが必要な場合。

ソフトウェアテスト自動化で推奨されるクエリの優先順位を示す、インフォグラフィックのピラミッドチャート。

タイミングと意図でQueryを選ぶ

各クエリファミリーには、それぞれ明確な目的があります:

  • 同期的な存在確認: 使用する getByRole, getByText、または別の getBy クエリを、要素がすでに存在しているはずの場合に使用します。存在しない場合、テストは即座に失敗します。
  • 非同期の出現確認: 使用する findByRole または findByText レンダリングやインタラクションが非同期更新を引き起こす場合。
  • 存在しないことの確認: 使用する queryByText または queryByTestId 要素が存在しない可能性があり、例外ではなく null が返ることを想定している場合。
  • フォールバックセレクタ: 使用する getByTestId 意図的に使用します。複雑なコントロールに永続的なフックを提供しますが、アプリ全体でアクセシブルなラベルを置き換えるべきではありません。

イベントに焦点を当てたテストでは、 fireEvent.press が直接的です:

fireEvent.press(screen.getByRole('button', { name: 'Save' }));

使用する userEvent インストールされているバージョンがより現実的なインタラクションシーケンスをサポートする場合。いずれにせよ、結果の UI をアサートします:

expect(await screen.findByText('Saved')).toBeTruthy();

ハンドラーアサーションは、パブリックな契約がイベントコールバックであるコンポーネントに適しています。ユーザージャーニーが機能していることを示す唯一の証拠としては弱いです。

Snapshotよりも具体的なAssertionを優先する

良いアサーションは画面を記述します:

expect(screen.getByText('Account created')).toBeTruthy();
expect(screen.getByRole('button', { name: 'Continue' })).toBeEnabled();

アクセシビリティ状態、選択状態、表示されるバリデーションフィードバックも検証できます。内部配線を確認することは避けてください:

expect(screen.getByTestId('submit-button').props.onPress).toBeDefined();

そのアサーションはプロップが存在することを証明するだけで、機能が動作することを証明しません。小さな意図的なスナップショットは構造の変化を検出できますが、大きなナビゲーションや画面のスナップショットはノイジーなレビューを生み、説明のない更新を承認しやすくします。

Testing Library パッケージはより広い testing-library npm 組織に属しています。アクティブなパッケージには @testing-library/react-nativeがあり、バージョン 13.3.3 は 2026 年に公開されました、プロジェクトの リポジトリ情報によると。共有クエリ規約はプラットフォーム間で役立ちますが、どのアサーションがプロダクトの動作を表すかを決定するものではありません。

Jestのコンポーネントテスト手法をより広く比較したい場合は、こちらの Reactのユニットテストガイドを参照してください。RNTLは依然としてJavaScriptでレンダリングされたコンポーネントの境界までしかテストできません。ネイティブ権限、実際のナビゲーションスタック、デバイスのキーボード、フレームタイミング、メモリの挙動には、コンポーネントのモックを増やすのではなく、E2Eやパフォーマンスツールが必要です。

以下の動画では、実際のコンテキストにおけるクエリとアサーションのワークフローを実演しています。

コンポーネント、Hooks、Navigationの実践パターン

有用なテストスイートは、アプリの構造に沿って作られます。Presentationalコンポーネントには振る舞いを直接テストし、Hooksには入出力を制御し、Navigationには十分リアルなProviderを、ネイティブモジュールには実機検証とは明確に区別できるMockを用意します。

木製のデスクに置かれたモダンなノートPCに、React Native codeとモバイルアプリのモックアップが表示されている。

Presentationalコンポーネント

コンポーネントのテストは、その公開インターフェースに沿って書きましょう:

const onSelect = jest.fn();

render(
  <PlanCard
    title="Team"
    description="Shared workspace"
    onSelect={onSelect}
  />
);

fireEvent.press(screen.getByRole('button', { name: 'Choose Team' }));

expect(onSelect).toHaveBeenCalled();

ラベルはUIと完全に一致させてください。重要なのは、ユーザーやアクセシビリティサービスと同じ方法でコントロールを見つけ、重要な表示結果やコールバックを確認することです。デフォルトですべての子コンポーネントをMockしないでください。テスト対象の振る舞いを邪魔する場合にだけ、コストの高い境界や無関係な境界をMockしましょう。

カスタムHookでは、以下を使います: renderHook インストール済みのRNTLバージョンで利用できる場合:

const { result } = renderHook(() => useSearch());

await act(async () => {
  await result.current.submit('query');
});

expect(result.current.status).toBe('success');

Hookのテストでは、アプリ全体を再現するのではなく、ネットワークやリポジトリの境界を制御すべきです。Hookの状態が有用なUIになるかを確認するため、画面は別にテストしましょう。

Navigationの振る舞いについては、実際の NavigationContainer すべてのNavigationメソッドをMockするより、小さなテスト用Navigatorを使う方が価値が高いことが多いです。表示されているコントロールを押し、遷移先のコンテンツを待って、新しい画面の出力をアサートします。直接的な useNavigation 単一の型付きルートをディスパッチするだけの小さなボタンには mock が依然として適切ですが、ルートの登録、パラメータ、ネストされたナビゲーターの動作は検証されません。

非同期データにも同じ規律が必要です。API またはリポジトリのレスポンスを mock し、画面をレンダリングしてローディング状態をアサートし、リクエストを解決してから成功またはエラーの出力をアサートします。 findBy 期待される最終的な要素には queries を使用し、拒否されたリクエストを明示的に扱います。そうしないと、コンポーネントが意図した分岐に到達しなかったのにテストがパスしてしまう可能性があります。

Native moduleのモック

AsyncStorage、権限、カメラ、生体認証、プラットフォーム API の mock は、決定的な JavaScript テストに有用です。ただし、ネイティブ機能が動作する証拠にはなりません。mock の挙動はモジュールの契約に近づけ、テスト間で呼び出しをリセットし、ハッピーパスだけでなく失敗レスポンスもモデル化してください。

シナリオ RNTL と相性が良い E2E 検証が必要
フォームのバリデーションとエラー表示 はい 通常は不要
モックされたリポジトリからのローディング、成功、エラー UI はい 本番環境の重要なフロー
登録済み画面間のナビゲーション はい、テストナビゲーターを使用する場合 ジェスチャー、ディープリンク、プラットフォームの動作が重要な場合
AsyncStorage 状態に基づく判断 はい、制御されたモックを使用する場合 起動と永続化がネイティブライフサイクルと相互作用する場合
カメラ、生体認証、権限、またはプラットフォーム API JS フォールバックと分岐ロジック はい、実機または代表デバイスで
レイアウト、レンダリング性能、およびネイティブ code いいえ はい、実機または専用ツールで可能です

境界は実用的です。依存関係をモックしてJavaScriptの判断をテストし、その後、実機テストで依存関係の実際の挙動を確認します。

Flaky Testのデバッグ、CI、パフォーマンス確認

不安定なテスト(flaky test)は、制御されていない時間、共有状態、UIと競合するアサーションのいずれかに起因することが多いです。タイムアウトを延ばす前に、どの条件が当てはまるか特定してください。タイムアウトを長くするとスケジューリングの問題を隠し、スイート全体が遅くなります。

使用する findBy 更新後に表示されるはずの要素には、以下を使用します。 waitFor 状態の条件やモック呼び出しには、以下を使用します。fake timersが有効な場合は、インタラクションが必要な時点でタイマーを進め、その後、実際のタイマーに戻してください。 act この警告は、Reactが想定されるインタラクション境界の外で更新を検知したことを意味します。不足している awaitユーザーインタラクション、またはタイマーのフラッシュを修正してください。警告を抑制するのではなく。

CIの失敗を再現可能にする

信頼できるCIジョブは、lockfileからインストールし、ローカルと同じJestコマンドを実行し、モックの状態を分離します。テスト間でモック呼び出しをクリアし、モジュールレベルの状態が挙動に影響する場合はモジュールをリセットし、実行順序への依存を排除してください。Jestのキャッシュはフィードバックを高速化しますが、依存関係や設定の変更時には適切にキャッシュを無効化する必要があります。

CI でのみ失敗する場合は、コンポーネントを書き換える前に環境を比較してください。Node、パッケージマネージャー、Jest のワーカー設定、タイマー設定、環境変数を確認します。可能ならローカルで同じコマンドを再現し、失敗するテストを、差分が現れる最小のインタラクションまで絞り込みます。

Observability は別のギャップを埋めます。 アプリのヘルスモニタリング モック化したコンポーネントテストでは再現できない、実機やネイティブ統合に起因する失敗を含む本番エラーのコンテキストを提供します。

セキュリティに敏感なジャーニーには、2つ目の境界が必要です。バリデーションと状態遷移にはコンポーネントテストを使い、ハンドオフとネイティブ動作にはデバイスレベルのカバレッジを追加します。ワンタイム code フローについては、次のガイダンスを参照してください: SMS 認証フローをテストする方法 その統合がジャーニーの一部となる場合。

パフォーマンスは断定ではなく、計測対象として扱う

機能テストではリストがレンダリングされることを確認できますが、リファクタリングでレンダリング時間や回数が変わったかを確実に判定することはできません。テスト実行時にノイズが混入するためです。Reassure はシナリオのこれらの値を測定し、分散を減らすためにシナリオを繰り返し、統計的分析を適用してから有意な変化を報告します。その パフォーマンステストのドキュメント では、CI やプルリクエストレビューに適したレポートについても説明しています。

「このレンダリングは特定の閾値以下で完了しなければならない」といった固定のアサーションは避けてください。負荷の高い CI ワーカーでは誤った失敗を引き起こす可能性があり、緩い閾値では実際のリグレッションを見逃す可能性があります。リグレッションのシグナルには繰り返し測定を使用し、比較で有意な差が見られた場合は、コンポーネントとデバイスプロファイルを確認してください。

測定ルール: 機能テストは動作が正しいかを答え、パフォーマンスツールは測定したシナリオが変化했かを答えます。これらの質問は分けてください。

まとめと今後の展望

React Native Testing Library は、テスト戦略の広範で高速なレイヤーに属します。コンポーネントの動作、表示状態の変化、アクセシビリティの結果、バリデーション、モックされたデータ状態、JavaScript コンポーネント間の統合をカバーすべきです。これらのテストはユーザーが観察できることに焦点を当て、失敗が大きなレンダリングツリーではなく特定の動作を指すようにしてください。

より小さなデバイスベースのレイヤーは、モックが嘘をつく可能性のあるフローを保護すべきです。React Native の テスト概要 RNTLは完全なReact Nativeランタイムを提供しておらず、ネイティブ機能のテストには対応していないと明記されています。同様のガイダンスでは、認証、決済、アプリのコア機能といった重要なフローについては、DetoxなどのE2Eツールと組み合わせることを推奨しています。

実践的な移行パス

既存のテストスイートを一度に書き換える必要はありません。

  1. 価値のあるビジネステストは残してください。 明確なフィードバックが得られる純粋な状態やドメインロジックは、焦点を絞ったユニットテストに移行してください。
  2. まずは実装に依存したアサーションを置き換えましょう。 propsや内部状態のチェックを、表示される出力、アクセシビリティ状態、インタラクションの結果に変更します。
  3. スナップショットを小さくします。 レビュアーが理解して保守できるスナップショットだけを残します。
  4. ネイティブ境界のテストカバレッジを追加します。 モック化した重要なモジュールごとに、検証が必要なデバイスの挙動を特定します。
  5. クリティカルなジャーニーを保護します。 認証、決済、コアナビゲーション、権限設定など、ネイティブの挙動が結果に影響するフローにE2Eテストを追加します。
  6. パフォーマンスに敏感な画面は個別に計測します。 リストやフィード、負荷の高いレンダリングパスでは、Jestの実行時間から推測せず、パフォーマンス比較を繰り返します。

リリースの信頼性を高めるため、テストスイートを以下に連携させます。 CI/CD統合テスト また、リリース前に適切なテストレイヤーを必須とします。検証後にJavaScriptやアセットの修正をユーザーに届ける必要がある場合、Capgo はCapacitorJSおよびElectronアプリ向けに、ロールアウト制御とロールバック保護を備えた署名済みWebバンドルを対象チャネルへ配信できます。この配信ワークフローはReact Nativeのデバイステストに代わるものではありませんが、動作するレイヤーで振る舞いを検証するという同じ原則を示しています。

正しい問いは、React Native Testing Libraryでアプリ全体をテストできるかどうかではありません。実際にはできませんし、公式の境界は役に立ちます。正しい問いは、重要なリスクごとに、それを顕在化できる環境でテストが実行されているかどうかです。


Capgo は、検証済みのJavaScriptとアセットの変更を、CapacitorJSとElectronのチーム向けに、対象チャネル、ロールアウトの可視化、ロールバック保護を備えた制御された配信につなぎます。詳細は Capgo コンポーネント、E2E、CIのテストワークフローとどう併用できるかをご覧ください。

Capacitor アプリ向けの Live Updates

Web レイヤーのバグが本番で発生したら、App Store の審査を何日も待たずに Capgo で修正を配信しましょう。ネイティブの変更は通常の審査フローに残したまま、ユーザーはバックグラウンドでアップデートを受け取れます。

最新のブログ記事

Capgoは、真にプロフェッショナルなモバイルアプリを作るために必要な最高のインサイトを提供します。