はじめに (対象読者・この記事でわかること)
この記事は、Flutter で画面遷移を伴うインテグレーションテストを書いていて「画面遷移先の Widget が見つからない」「No MediaQuery found エラーが出る」といった問題にハマっている開発者を対象にしています。
記事を読むことで、以下のことがわかります。
- なぜ画面遷移直後に Widget が見つからないのか
tester.pumpAndSettleとtester.pumpの違いと使い分け- 実装レベルで避けるべき落とし穴と、実際に動くテストコードの書き方
前提知識として、Flutter の Widget テストや integration_test パッケージの基本的使用方法を理解していることが望ましいです。
前提知識
この記事を読み進める上で、以下の知識があるとスムーズです。
- Flutter の Widget / Integration テストの基本(testWidgets, tester.tap, tester.pump など)
- MaterialApp や Navigator を使った画面遷移の仕組み
- integration_test パッケージの導入方法(flutter_test とは別に integration_test/test_bundle.dart を生成していること)
問題の概要:画面遷移後の Widget が見つからない
インテグレーションテストでは、実端末に近い環境でアプリを起動し、ボタンタップ→画面遷移→次画面の Widget の有無を検証、という流れが一般的です。
しかし、次のようなコードを書くとテストが失敗します。
Dartawait 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 が非同期にビルドされるため、以下の問題が起きます。
- 古い Widget ツリーが dispose される
- 新しい MediaQuery がまだ構築されていない
find.byTypeが古いツリーの中を探してしまう
結果、「Widget が見つからない」あるいは「MediaQuery がない」というエラーが出ます。
解決策:明示的に pump 回数を制御する
解決策は単純です。「遷移直後は pumpAndSettle ではなく、固定回数の pump を使い、さらに Finder で次画面のルートを探す範囲を限定する」ことです。
Dartawait 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 を探すようにします。
Dartawait 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.descendantやKeyを使って探索範囲を限定すると、テストが安定する
この知識を活用すれば、画面遷移を伴う E2E テストも自信を持てるようになります。
次回は、画面遷移後の API 通信をモックしてオフラインでもテストを回す方法を紹介します。
参考資料
