🤯
TextFieldが含まれているCheckboxListTileのWidgetテストでハマった話
二つの CheckboxListTile
の動作
このようなCheckboxListTile
ウィジェットがあったとします。
一つはチェックボックスとテキストがあるシンプルなものです。
もう一つはチェックボックスをオンにするとTextField
が有効になり、テキストが入力できる仕様です。
アンケートフォームのような画面で、"その他"を選択するとフリーテキストで入力できるようになるイメージです。
ではこのCheckboxListTile
をタップした時、ちゃんとチェックボックスがオンオフに変化するかテストしてみましょう。
遭遇した問題
まず一つ目のシンプルなCheckboxListTile
のテストは以下のように書けます。
テストは成功します。
testWidgets("CheckboxListTileがオンオフできること", (tester) async {
await tester.pumpWidget(MyApp());
final tileFinder = find.byKey(ValueKey("checkbox_list_tile"));
// 初期値はfalse
expect(tester.widget<CheckboxListTile>(tileFinder).value, isFalse);
// タップ
await tester.tap(tileFinder);
await tester.pumpAndSettle();
// 値がtrueになる
expect(tester.widget<CheckboxListTile>(tileFinder).value, isTrue);
// タップ
await tester.tap(tileFinder);
await tester.pumpAndSettle();
// 値がfalseになる
expect(tester.widget<CheckboxListTile>(tileFinder).value, isFalse);
});
続いてTextField
が含まれているCheckboxListTile
のテストを書いてみましょう。
なんということでしょう。チェックボックスをオフにする操作をしてもチェックボックスの値が変わらず、テストが失敗します。
testWidgets("TextField付きのCheckboxListTileがオンオフできること", (tester) async {
await tester.pumpWidget(MyApp());
final tileFinder = find.byKey(
ValueKey("checkbox_list_tile_with_text_field"),
);
// 初期値はfalse
expect(tester.widget<CheckboxListTile>(tileFinder).value, isFalse);
// タップ
await tester.tap(tileFinder);
await tester.pumpAndSettle();
// 値がtrueになる
expect(tester.widget<CheckboxListTile>(tileFinder).value, isTrue);
// タップ
await tester.tap(tileFinder);
await tester.pumpAndSettle();
// **値がfalseにならない**
expect(tester.widget<CheckboxListTile>(tileFinder).value, isFalse);
});
解法
タップすべきなのはCheckboxListTile
ではなく、その中に含まれるCheckbox
のウィジェットです。
find.descendant
を使って、CheckboxListTile
の子のCheckbox
を見つけてタップするようにしましょう。
testWidgets("TextField付きのCheckboxListTileがオンオフできること", (tester) async {
await tester.pumpWidget(MyApp());
final tileFinder = find.byKey(
ValueKey("checkbox_list_tile_with_text_field"),
);
+ final checkboxFinder = find.descendant(
+ of: tileFinder,
+ matching: find.byType(Checkbox),
+ );
// 初期値はfalse
expect(tester.widget<CheckboxListTile>(tileFinder).value, isFalse);
// タップ
- await tester.tap(tileFinder);
+ await tester.tap(checkboxFinder);
await tester.pumpAndSettle();
// 値がtrueになる
expect(tester.widget<CheckboxListTile>(tileFinder).value, isTrue);
// タップ
- await tester.tap(tileFinder);
+ await tester.tap(checkboxFinder);
await tester.pumpAndSettle();
// 値がfalseになる
expect(tester.widget<CheckboxListTile>(tileFinder).value, isFalse);
});
原因
どうやらCheckboxListTile
をタップしているつもりでしたが、その中のTextField
をタップしている扱いとなっているようです。
FocusNode
をセットしてみると、TextField
がフォーカスされていることがわかります。
ちゃんと目的のウィジェットを明示してタップするようにしましょう。(半日吹き飛びました)
Discussion