はじめに (対象読者・この記事でわかること)
この記事は、FlutterでWidgetTestを書いていて「非同期処理(Future)を含むWidgetのテストが不安定」「タイミングによって成功したり失敗したりする」という悩みを抱えている開発者を対象にしています。
この記事を読むことで、WidgetTest内でFutureを含む処理を確実に待つ方法がわかり、テストの安定性が大幅に向上します。具体的には、pumpAndSettleやwaitForを使った正しい待機方法や、非同期処理を含むWidgetのテストパターンを習得できます。
前提知識
この記事を読み進める上で、以下の知識があるとスムーズです。
- Flutterの基本的なWidgetTestの書き方(
testWidgetsの使い方) - Dartにおける
Futureやasync/awaitの基本的な動作 WidgetTesterの基本的なメソッド(pumpなど)の使い方
WidgetTestで非同期処理を待つことの重要性
FlutterでWidgetTestを書いていると、「Widget内で非同期処理(API呼び出しやSharedPreferencesの読み書きなど)が発生しているにもかかわらず、テストがタイミングによって失敗する」という問題に遭遇することがあります。
たとえば、以下のようなWidgetをテストするとき:
Dartclass UserInfoWidget extends StatelessWidget { @override Widget build(BuildContext context) { return FutureBuilder<String>( future: fetchUserName(), // 非同期でユーザー名を取得 builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return CircularProgressIndicator(); } if (snapshot.hasData) { return Text('こんにちは、${snapshot.data}さん'); } return Text('ユーザー情報を取得できませんでした'); }, ); } }
このWidgetをテストする際、単純にpump()を呼ぶだけでは非同期処理が完了する前にテストが進んでしまい、意図したアサーションが通らないことがあります。これを防ぐためには、非同期処理を正しく待つ必要があります。
WidgetTestでFutureを待つ正しい方法
ここでは、WidgetTest内で非同期処理を確実に待つための具体的な方法を解説します。
ステップ1: pumpAndSettleを使った基本的な待機
最も簡単で有効な方法は、WidgetTester.pumpAndSettle()を使うことです。これは、Widgetツリーが安定する(再描画がなくなる)まで待機するメソッドです。
DarttestWidgets('UserInfoWidgetが正しく表示される', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp(home: UserInfoWidget())); // 非同期処理が完了するまで待機 await tester.pumpAndSettle(); expect(find.text('こんにちは、太郎さん'), findsOneWidget); });
pumpAndSettle()は、内部でpump()を繰り返し呼び出し、指定された期間(デフォルトは100ms)再描画がなければ完了と判断します。これにより、非同期処理が終わるまでテストが進まなくなります。
ステップ2: waitForを使ったカスタム条件の待機
pumpAndSetle()では待てないケース(たとえば、特定のWidgetが表示されるまで待つなど)には、waitForを使います。waitForは、指定された条件が満たされるまで待機します。
DarttestWidgets('特定のWidgetが表示されるまで待つ', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp(home: UserInfoWidget())); // 特定のテキストが表示されるまで待機 await tester.waitFor(find.text('こんにちは、太郎さん')); expect(find.text('こんにちは、太郎さん'), findsOneWidget); });
waitForは、条件が満たされるかタイムアウト(デフォルト10秒)になるまで、定期的にpump()を呼び出します。これにより、非同期処理が完了して特定のWidgetが表示されるまで確実に待てます。
ハマった点とその解決策
問題: pumpAndSettleがタイムアウトする
pumpAndSettle()を使ったところ、以下のようなエラーが発生しました:
pumpAndSettle timed out
これは、非同期処理が永遠に終わらない(たとえば、定期的に再描画が発生するWidget)場合に発生します。
解決策: タイムアウト時間を調整する、またはpumpを手動で制御する
以下のように、タイムアウト時間を明示的に指定することで回避できます:
Dartawait tester.pumpAndSettle(Duration(seconds: 1)); // 1秒でタイムアウト
あるいは、手動でpump()を呼び出す回数を制御することも有効です:
Dartfor (int i = 0; i < 10; i++) { await tester.pump(Duration(milliseconds: 100)); if (find.text('こんにちは、太郎さん').evaluate().isNotEmpty) { break; } }
このように、非同期処理の完了を正しく待つことで、WidgetTestを安定稼働させることができます。
まとめ
本記事では、FlutterのWidgetTestで非同期処理(Future)を確実に待つ方法を解説しました。
pumpAndSettle()を使うことで、Widgetツリーが安定するまで待てるwaitFor()を使うことで、特定の条件が満たされるまで待てる- タイムアウト対策として、待機時間を明示的に指定する方法
この記事を通して、Flutterで非同期処理を含むWidgetのテストが安定し、CI/CDでの自動テストも信頼できるようになるでしょう。
今後は、モックを使った非同期処理のテストや、並列処理を含むWidgetのテスト方法についても記事にする予定です。
参考資料
