如何为使用Flutter日期时间选择器的Widget编写日期时间选择单元测试?
测试EditableDateTime组件的日期+时间选择功能
要测试同时选择日期和时间的功能,核心是在日期选择完成后,操作弹出的时间选择器,再验证最终的日期时间值。以下是具体实现步骤和完整代码:
关键实现逻辑
- 触发日期选择流程:点击文本框后,先选择目标日期并确认,此时会自动弹出时间选择器
- 操作时间选择器:针对24小时制的时间选择器,直接查找目标小时、分钟的文本按钮并点击,最后确认
- 验证结果:检查文本框内容是否更新为修改后的完整日期时间
新增测试用例
在原测试组中加入以下测试用例,实现日期与时间的联合测试:
testWidgets( 'Selecting date and time together', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( body: EditableDateTime( dateTimeTitle: 'Date time', dateTimePickerController: dateTimePickerController, ), ), ), ); // 初始化初始时间 dateTimePickerController.text = '2022-09-20 12:45'; TextField textField = tester.widget(find.byKey(const Key('editableDateTimeTextField'))); expect(textField.controller!.text, '2022-09-20 12:45'); // 触发选择弹窗 await tester.tap(find.byKey(const Key('editableDateTimeTextField'))); await tester.pumpAndSettle(); // 选择日期:9月14日 await tester.tap(find.text('14')); await tester.tap(find.text('OK')); await tester.pumpAndSettle(); // 选择时间:15点30分(24小时制) await tester.tap(find.text('15')); // 选择小时 await tester.tap(find.text('30')); // 选择分钟 await tester.tap(find.text('OK')); await tester.pumpAndSettle(); // 验证最终结果 expect(textField.controller!.text, '2022-09-14 15:30'); }, );
完整测试代码
将上述用例整合后的完整代码如下:
import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:intl/intl.dart'; /// EditableDateTime widget test code Future<void> main() async { final Finder previousMonthIcon = find.byWidgetPredicate((Widget w) => w is IconButton && (w.tooltip?.startsWith('Previous month') ?? false)); TextEditingController dateTimePickerController = TextEditingController(); group( 'EditableDateTime widget testing', () { testWidgets( 'Setting date day only', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( body: EditableDateTime( dateTimeTitle: 'Date time', dateTimePickerController: dateTimePickerController, ), ), ), ); dateTimePickerController.text = '2022-09-20 12:45'; TextField textField = tester.widget(find.byKey(const Key('editableDateTimeTextField'))); expect(textField.controller!.text, '2022-09-20 12:45'); await tester.tap(find.byKey(const Key('editableDateTimeTextField'))); await tester.pumpAndSettle(); await tester.tap(find.text('14')); // set day await tester.tap(find.text('OK')); await tester.pumpAndSettle(); await tester.tap(find.text('OK')); expect(textField.controller!.text, '2022-09-14 12:45'); }, ); testWidgets( 'Selecting previous date month and setting day only', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( body: EditableDateTime( dateTimeTitle: 'Date time', dateTimePickerController: dateTimePickerController, ), ), ), ); dateTimePickerController.text = '2022-09-20 12:45'; TextField textField = tester.widget(find.byKey(const Key('editableDateTimeTextField'))); expect(textField.controller!.text, '2022-09-20 12:45'); await tester.tap(find.byKey(const Key('editableDateTimeTextField'))); await tester.pumpAndSettle(); await tester.tap(previousMonthIcon); await tester.pumpAndSettle(const Duration(seconds: 1)); await tester.tap(find.text('6')); // set day await tester.tap(find.text('OK')); await tester.pumpAndSettle(); await tester.tap(find.text('OK')); expect(textField.controller!.text, '2022-08-06 12:45'); }, ); // 新增日期时间联合测试用例 testWidgets( 'Selecting date and time together', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( body: EditableDateTime( dateTimeTitle: 'Date time', dateTimePickerController: dateTimePickerController, ), ), ), ); dateTimePickerController.text = '2022-09-20 12:45'; TextField textField = tester.widget(find.byKey(const Key('editableDateTimeTextField'))); expect(textField.controller!.text, '2022-09-20 12:45'); await tester.tap(find.byKey(const Key('editableDateTimeTextField'))); await tester.pumpAndSettle(); // 选择日期 await tester.tap(find.text('14')); await tester.tap(find.text('OK')); await tester.pumpAndSettle(); // 选择时间(24小时制) await tester.tap(find.text('15')); await tester.tap(find.text('30')); await tester.tap(find.text('OK')); await tester.pumpAndSettle(); expect(textField.controller!.text, '2022-09-14 15:30'); }, ); }, ); } /// EditableDateTime stateless widget definition class EditableDateTime extends StatelessWidget { EditableDateTime({ Key? key, required this.dateTimeTitle, required this.dateTimePickerController, }) : super(key: key); static final DateFormat englishDateTimeFormat = DateFormat("yyyy-MM-dd HH:mm"); DateTime _selectedDate = DateTime.now(); TimeOfDay _selectedTime = TimeOfDay.now(); DateTime _dateTime = DateTime.now(); final String dateTimeTitle; final TextEditingController dateTimePickerController; void _updateDateTimePickerValues() { _selectedDate = _dateTime; _selectedTime = TimeOfDay(hour: _dateTime.hour, minute: _dateTime.minute); dateTimePickerController.text = englishDateTimeFormat.format(_dateTime); } // Select for Date Future<DateTime?> _selectDatePickerDate(BuildContext context) async { final DateTime? selectedDate = await showDatePicker( context: context, initialDate: _selectedDate, firstDate: DateTime(2020), lastDate: DateTime(2100), ); if (selectedDate == null) { // User clicked on Cancel button return null; } else { if (selectedDate != _selectedDate) { _selectedDate = selectedDate; } } return _selectedDate; } Future<TimeOfDay?> _selectDatePickerTime(BuildContext context) async { final TimeOfDay? selectedTime = await showTimePicker( context: context, initialTime: _selectedTime, builder: ( BuildContext context, Widget? childWidget, ) { return MediaQuery( data: MediaQuery.of(context).copyWith( alwaysUse24HourFormat: true, ), child: childWidget!, ); }, ); if (selectedTime == null) { // User clicked on Cancel button return null; } else { if (selectedTime != _selectedTime) { _selectedTime = selectedTime; } } return _selectedTime; } Future _selectDatePickerDateTime(BuildContext context) async { final DateTime? date = await _selectDatePickerDate(context); if (date == null) { // User clicked on date picker dialog Cancel button. In // this case, the time picker dialog is not displayed and // the _dateTime value is not modified. return; } final TimeOfDay? time = await _selectDatePickerTime(context); if (time == null) { // User clicked on time picker dialog Cancel button. In // this case, the _dateTime value is not modified. return; } // setState(() { _dateTime = DateTime( date.year, date.month, date.day, time.hour, time.minute, ); // }); dateTimePickerController.text = englishDateTimeFormat.format(_dateTime); } @override Widget build(BuildContext context) { // print('_EditableDateTimeState.build()'); return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( dateTimeTitle, style: TextStyle( color: Colors.yellow.shade300, fontSize: 18.0, ), ), const SizedBox( height: 5.0, ), SizedBox( // Required to fix Row exception // layoutConstraints.maxWidth < double.infinity. width: 170, child: Theme( data: Theme.of(context).copyWith( textSelectionTheme: TextSelectionThemeData( selectionColor: Colors.blue.shade900, ), ), child: TextField( key: const Key('editableDateTimeTextField'), decoration: const InputDecoration.collapsed(hintText: ''), style: const TextStyle( color: Colors.white, fontSize: 18.0, fontWeight: FontWeight.normal), controller: dateTimePickerController, readOnly: true, onTap: () { // initializing the date and time dialogs with the // currently displayed date time value ... DateTime currentDateTime = englishDateTimeFormat .parse(dateTimePickerController.text); _selectedTime = TimeOfDay(hour: currentDateTime.hour, minute: currentDateTime.minute); _selectedDate = currentDateTime; _selectDatePickerDateTime(context); }, ), ), ), ], ), ], ); } }
注意事项
- 时间选择器采用24小时制,直接通过文本查找对应时间按钮即可(如
find.text('08')对应8点) - 每次弹窗操作后必须调用
await tester.pumpAndSettle(),确保UI完全渲染完成 - 若需测试特殊时间场景(如分钟为0、跨小时切换),只需修改时间选择的文本值即可
内容的提问来源于stack exchange,提问作者Jean-Pierre Schnyder
相关产品推荐
相关产品推荐

