🌊

FlutterのDatepickerのテストを書く

2023/10/10に公開

FlutterのDatePickerを使ったウィジェットのテストを書いたときの備忘録です。
Datepicker自体のテストではなく、ちゃんとDatepickerを使ってstateを更新できるかみたいなテストです。

テキストフィールドをタップしたらDatepickerを開く

今回はDatepickerを開くトリガーとしてテキストフィールドをタップしたらにしています。
TextFieldにkeyを設定してtapすることでDatepickerを開くようにしています。

TextField(
	key: const Key('registered-at-field'),
	controller: dateTextController,
	decoration: const InputDecoration(hintText: '日付を入力', labelText: '日付'),
	onTap: handleSelectRegisteredAt,
)
await tester.tap(find.byKey(const Key('registered-at-field')));

Clock.nowで初期表示の日時を固定する

Datepickerを開いた時に表示されるカレンダーの年月を操作日にしています。
これだとテストを実装した場合実行環境に依存してしまいます。
LocalDate.now()みたいな現在日を取得するロジックを使ったコードをテストしたいときのあるあるな悩みだと思います。
Flutterだとclockというライブラリを使うことで解決できるようです。

https://pub.dev/packages/clock

DateTime.now()を使っている箇所をclock.now()に置き換えます。
DatepickerのinitialDateに初期表示したいDateTimeを渡しています。

final handleSelectRegisteredAt = useCallback(() async {
      final registeredAt = await showDatePicker(
          context: context,
          initialDate: clock.now(), 
          firstDate: DateTime.parse(ReleaseDate.stringValue),
          lastDate: clock.now());
      if (registeredAt != null) {
        dateTextController.text = DateFormat('yyyy/MM/dd').format(registeredAt);
        viewModel.setRegisteredAt(registeredAt);
      }
    }, [viewModel, context, dateTextController, state.registeredAt]);

clock.now()を使ったテストコードをwithClock内で実行することでnow()の日時を固定できるようです。

withClock(Clock.fixed(DateTime(2023, 8, 31)), () async {
}

findTextで日付を選択し、Datepickerを閉じる

初期表示のカレンダーが固定できたので表示される日にちも決め打ちでテストできます。

await tester.tap(find.text('30'));
await tester.pumpAndSettle();
await tester.tap(find.text('OK'));
await tester.pumpAndSettle();

無事現在日を取得するロジックを使ったテストを書くことができました。
今回の実装のソースコードです。

registration_page.dart
class RegistrationPage extends HookConsumerWidget {
  const RegistrationPage({Key? key}) : super(key: key);

  
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(registrationViewModelProvider);
    final viewModel = ref.watch(registrationViewModelProvider.notifier);

    final dateTextController = useTextEditingController(
        text: DateFormat('yyyy/MM/dd').format(state.registeredAt));

    final handleSelectRegisteredAt = useCallback(() async {
      final registeredAt = await showDatePicker(
          context: context,
          initialDate: clock.now(), 
          firstDate: DateTime.parse(ReleaseDate.stringValue),
          lastDate: clock.now());
      if (registeredAt != null) {
        dateTextController.text = DateFormat('yyyy/MM/dd').format(registeredAt);
        viewModel.setRegisteredAt(registeredAt);
      }
    }, [viewModel, context, dateTextController, state.registeredAt]);

    return Scaffold(
        body: Padding(
            padding: const EdgeInsets.all(16),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Column(children: [
                  TextField(
                    key: const Key('registered-at-field'),
                    controller: dateTextController,
                    decoration: const InputDecoration(
                        hintText: '日付を入力', labelText: '日付'),
                    onTap: handleSelectRegisteredAt,
                  )
              ])])));
  }
}
registration_page_test.dart
void main() {
  testWidgets(
      'Datepickerを使ったテスト',
      (tester) async {
    withClock(Clock.fixed(DateTime(2023, 8, 31)), () async {
      final dummyCost =
          CostBuilder().setRegisteredAt(DateTime(2023, 8, 30)).build();
      await render(tester);
      await tester.tap(find.byKey(const Key('registered-at-field')));
      await tester.pumpAndSettle();
      await tester.tap(find.text('30'));
      await tester.pumpAndSettle();
      await tester.tap(find.text('OK'));
      await tester.pumpAndSettle();
      final submitButton = find.byKey(const Key('register-button'));
      await tester.tap(submitButton);
      await tester.pumpAndSettle();

      verify(mockCostRepository.save(argThat(matchingWithoutId(dummyCost))))
          .called(1);
    });
  });
}

FlutterでSpenderというアプリを作っています。
よかったらこちらも見てみてください🤖

https://note.com/nugget_okapi/n/n7c52a591b16d

Discussion