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

この記事は、FlutterでWidgetTestを書いていて「非同期処理(Future)を含むWidgetのテストが不安定」「タイミングによって成功したり失敗したりする」という悩みを抱えている開発者を対象にしています。

この記事を読むことで、WidgetTest内でFutureを含む処理を確実に待つ方法がわかり、テストの安定性が大幅に向上します。具体的には、pumpAndSettlewaitForを使った正しい待機方法や、非同期処理を含むWidgetのテストパターンを習得できます。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。

  • Flutterの基本的なWidgetTestの書き方(testWidgetsの使い方)
  • DartにおけるFutureasync/awaitの基本的な動作
  • WidgetTesterの基本的なメソッド(pumpなど)の使い方

WidgetTestで非同期処理を待つことの重要性

FlutterでWidgetTestを書いていると、「Widget内で非同期処理(API呼び出しやSharedPreferencesの読み書きなど)が発生しているにもかかわらず、テストがタイミングによって失敗する」という問題に遭遇することがあります。

たとえば、以下のようなWidgetをテストするとき:

Dart
class 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ツリーが安定する(再描画がなくなる)まで待機するメソッドです。

Dart
testWidgets('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は、指定された条件が満たされるまで待機します。

Dart
testWidgets('特定の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を手動で制御する

以下のように、タイムアウト時間を明示的に指定することで回避できます:

Dart
await tester.pumpAndSettle(Duration(seconds: 1)); // 1秒でタイムアウト

あるいは、手動でpump()を呼び出す回数を制御することも有効です:

Dart
for (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のテスト方法についても記事にする予定です。

参考資料