FlutterのWidgetテスト時に、GetXのGet.dialogで呼び出したダイアログのリターンが正しく取得できないという問題があり、その対方法について記述する。
対応方法
気を付けなければいけないのは以下の2点
GlobalKey<NavigatorState>()
を作成しそれをGetMaterialAppとGet.dialogに渡す。- dialogのリターンはFuture<T?>で受け取り、exceptでawaitする。
テスト部分のみ切り出したソースは以下の通り。
testWidgets('Action', (WidgetTester tester) async {
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
final testWidget = GetMaterialApp(
navigatorKey: navigatorKey,
locale: const Locale('en', 'US'),
home: const Material(child: Text("X")));
await tester.pumpWidgetBuilder(testWidget);
final result = Get.dialog<bool>(
const CommonDialog("title", "description\n2line"),
navigatorKey: navigatorKey);
await tester.pumpAndSettle(const Duration(seconds: 1));
await tester.tap(find.text("commonDialogOk".tr));
expect(await result, true);
});
GlobalKey<NavigatorState>()について
あくまでテスト環境のみなのだが、Get.toは問題ないのだが、Get.dialogだとダイアログ側ウィジェットでGet.back(値)で値を返す処理を行うと正しく値が返らなかった。
その結果テスト実行時expectのawait resultで永久ループ状態に陥ることになった。
この部分の対策を取るのに4日ほど調べてしまった。
ググっても全く情報がなかったし。
GetX開発者に聞いたわけではないので想像なのだけど、理由は以下の様になると思える。
- Get.backでリターンを返す場合、内部的にはNavigator.popで返すようにしている。
- Get.dialogで呼び出したダイアログでGet.backを呼び出してもNavigator.popが呼び出されなかった。
- GetXの状態管理でGet.dialogで呼び出されたものが匿名ページとして管理されていないかった。
本来Get.dialogで呼び出されると以下の様に数値の名前でページが開かれたと認識される。
テスト環境だとダイアログ自体は表示されているのだけど、上の様にOPEN DIALOGとして認識されていなかった。そのためリターンを返すダイアログだと認識されていなかったのかも。 - GetXでダイアログが表示された場合、GetX用のNavigatorObserverが呼び出され、開いたダイアログがGetXの管理下に置かれる。
ただ当初はダイアログが呼び出されてもGetX用のNavigatorObserverが呼び出されなかった。
このNavigatorObserverはGetMaterialAppで管理されているnavigatorKeyに定義されている。 - 本来GetMaterialAppにnavigatorKeyを書かないと、そこで作成されたものがGetX内で使いまわされると思ったのだけど、Get.dialogだけはその対応から外れていたようだ。
そのため、Get.dialogだけGetX用のNavigatorObserverが呼び出されなかった。
上記理由により、外部でGlobalKey<NavigatorState>()を用意し、GetMaterialAppとGet.dialogに渡すようにしたことで、想定通りに動作するようになった。
exceptでawaitする件について
Get.dialogだけではなくpop(値)で呼び出したウィジェットのリターンを受け取るケースでの一般的な書き方のようだ。
Get.dialog側にawaitを書くと、そこで待機状態になり、ダイアログを閉じるためのテストドライブができなくなるのがその理由。
Future<T?>形式で受け取りダイアログを閉じる操作をしたのち、awaitで値判断をする。
コメント