はじめに (対象読者・この記事でわかること)

この記事は、Flutter で画面遷移を伴うインテグレーションテストを書いていて「画面遷移先の Widget が見つからない」「No MediaQuery found エラーが出る」といった問題にハマっている開発者を対象にしています。
記事を読むことで、以下のことがわかります。

  • なぜ画面遷移直後に Widget が見つからないのか
  • tester.pumpAndSettletester.pump の違いと使い分け
  • 実装レベルで避けるべき落とし穴と、実際に動くテストコードの書き方

前提知識として、Flutter の Widget テストや integration_test パッケージの基本的使用方法を理解していることが望ましいです。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。 - Flutter の Widget / Integration テストの基本(testWidgets, tester.tap, tester.pump など) - MaterialAppNavigator を使った画面遷移の仕組み - integration_test パッケージの導入方法(flutter_test とは別に integration_test/test_bundle.dart を生成していること)

問題の概要:画面遷移後の Widget が見つからない

インテグレーションテストでは、実端末に近い環境でアプリを起動し、ボタンタップ→画面遷移→次画面の Widget の有無を検証、という流れが一般的です。
しかし、次のようなコードを書くとテストが失敗します。

Dart
await tester.tap(find.byKey(Key('navigate_button'))); await tester.pumpAndSettle(); // 画面遷移を待つ expect(find.byType(NextScreen), findsOneWidget); // ← ここでエラー

エラーメッセージは次の通りです。

══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞══
The following TestFailure was thrown running a test:
No MediaQuery found.
NextScreen widgets require a MediaQuery widget ancestor.

「なぜ? MaterialApp はちゃんとセットアップしたのに?」と混乱しますが、これは tester.pumpAndSettle の挙動と Navigator のビルドタイミングに起因します。

原因と解決策:正しいタイミングで Widget を探す

ステップ1:最小再現コードで問題を確認

まず、次の最小コードで現象を再現させます。

Dart
// test/integration_test/app_test.dart import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:myapp/main.dart' as app; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('次画面に遷移できる', (tester) async { app.main(); // runApp(MyApp()) await tester.pumpAndSettle(); // 初期表示まで待つ await tester.tap(find.byKey(Key('navigate_button'))); await tester.pumpAndSettle(); // ここで無限に待つ or エラー expect(find.byType(NextScreen), findsOneWidget); }); }

MyApp は通常通り MaterialApp(home: FirstScreen()) を返します。
この状態でテストを実行すると、pumpAndSettle が永遠に終了しない、あるいは No MediaQuery found が出ます。

ステップ2:pumpAndSettle の落とし穴を理解する

pumpAndSettle は「アニメーションがすべて完了するまで再帰的に pump し続ける」メソッドです。
しかし、次画面が MaterialPageRoute で覆われる瞬間には、新しい NavigatorState が非同期にビルドされるため、以下の問題が起きます。

  1. 古い Widget ツリーが dispose される
  2. 新しい MediaQuery がまだ構築されていない
  3. find.byType が古いツリーの中を探してしまう

結果、「Widget が見つからない」あるいは「MediaQuery がない」というエラーが出ます。

解決策:明示的に pump 回数を制御する

解決策は単純です。「遷移直後は pumpAndSettle ではなく、固定回数の pump を使い、さらに Finder で次画面のルートを探す範囲を限定する」ことです。

Dart
await tester.tap(find.byKey(Key('navigate_button'))); // 1フレームで画面遷移アニメーションを進める await tester.pump(); // 2フレーム目で新しいページがビルドされる await tester.pump(); // 次画面のルートがビルドされているはず expect(find.descendant( of: find.byType(MaterialApp), matching: find.byType(NextScreen), ), findsOneWidget);

pumpAndSettle を使いたい場合は、次のように「アニメーションが完了した後」に Widget を探すようにします。

Dart
await tester.tap(find.byKey(Key('navigate_button'))); // 画面遷移アニメーションが完了するまで待つ await tester.pumpAndSettle(Duration(milliseconds: 300)); expect(find.byType(NextScreen), findsOneWidget);

ポイントは、pumpAndSettle に明示的な duration を与えることで、「無限ループせずにタイムアウトしてくれる」ことです。

補足:Key を使った確実な探索

さらに確実にするには、次画面の Scaffold に Key を与えて探索します。

Dart
// NextScreen class NextScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( key: Key('next_screen_scaffold'), body: Center(child: Text('Next')), ); } } // テスト await tester.tap(find.byKey(Key('navigate_button'))); await tester.pumpAndSettle(Duration(milliseconds: 300)); expect(find.byKey(Key('next_screen_scaffold')), findsOneWidget);

これで、インテグレーションテストが安定して成功するようになります。

まとめ

本記事では、Flutter のインテグレーションテストで「画面遷移後の Widget が見つからない」問題の原因と回避策を解説しました。

  • tester.pumpAndSettle はアニメーション完了を待つが、新しい Widget ツリーが構築される前に探索してしまう
  • 遷移直後は pump を固定回数呼ぶか、pumpAndSettle に明示的な duration を与える
  • find.descendantKey を使って探索範囲を限定すると、テストが安定する

この知識を活用すれば、画面遷移を伴う E2E テストも自信を持てるようになります。
次回は、画面遷移後の API 通信をモックしてオフラインでもテストを回す方法を紹介します。

参考資料