Почему мой модульный тест не проходит, но код работает при ручном тестировании — состояние, похоже, не меняется при тестировании с помощью pumpAndSettle

#flutter #unit-testing

Вопрос:

В настоящее время я пишу свое первое приложение Flutter и добавляю первые в истории модульные тесты. Это также мой первый вопрос о StackOverflow, поэтому, пожалуйста, дайте мне знать, если я не предоставил достаточной информации (или слишком много информации!).

Мой домашний экран состоит из двух основных виджетов, которые я разделил на два разных класса. Я хотел бы написать ряд тестов для рабочего стола, начиная с первого главного виджета, который содержит две кнопки для прокрутки по месяцам. Я протестировал это вручную на физическом Android, iphone, а также на симуляторах — месяцы прокручиваются и работают так, как я ожидаю.

Контроллер месяца

Это начальный экран с двумя основными классами:

 //home_screen.dart  class HomeScreen extends StatefulWidget {  const HomeScreen({Key? key}) : super(key: key);   @override  _HomeScreenState createState() =gt; _HomeScreenState(); }  class _HomeScreenState extends Statelt;HomeScreengt; {  DateTime _maxDate = DateTime.now();  int _month = DateTime.now().month;  int _year = DateTime.now().year;   void setMMYY(int mm, int yy) {  setState(() {  _month = mm;  _year = yy;  });  }   void getMaxDate() {  //set the max date  }   @override  void initState() {  getMaxDate();  super.initState();  }   @override  Widget build(BuildContext context) {  return Scaffold(  appBar: AppBar(  title: const Text(constants.APP_NAME),  ),  body: Column(  children: [  Expanded(  flex: 1,  child: MonthController(setMMYY, _month, _year, _maxDate)),  Expanded(  flex: 9,  child: MyData(_month, _year),  ),  ],  ),  );  } }  

Это класс MonthController, который будет соответственно увеличивать/уменьшать месяц и отображать его в виджете с центральным текстом:

 //month_controller2.dart  class MonthController2 extends StatelessWidget {  final Function(int, int) setMMYY;  int month;  int year;  DateTime maxDate;   MonthController2(this.setMMYY, this.month, this.year, this.maxDate,  {Key? key})  : super(key: key);   void _nextMonth() {  if (month == 12) {  month = 1;  year  = 1;  } else {  month  = 1;  }  setMMYY(month, year);  }   void _prevMonth() {  if (month == 1) {  month = 12;  year -= 1;  } else {  month -= 1;  }  setMMYY(month, year);  }   bool _isPastMaxDate() {  DateTime thisMonth = DateTime(year, month, 1);  DateTime nextMonth = DateTime(year, thisMonth.month   1, 1);  if (nextMonth.isAfter(maxDate)) {  return true;  }   return false;  }   bool _isBeforeToday() {  DateTime thisMonth = DateTime(year, month, 1);  DateTime prevMonthTemp = thisMonth.subtract(  const Duration(days: 2),  );  DateTime prevMonth = DateTime(prevMonthTemp.year, prevMonthTemp.month, 1);  DateTime todayMonth =  DateTime(DateTime.now().year, DateTime.now().month, 1);  if (prevMonth.isBefore(todayMonth)) {  return true;  }   return false;  }   @override  Widget build(BuildContext context) {  var _theme = Theme.of(context);  return Row(  mainAxisAlignment: MainAxisAlignment.spaceEvenly,  children: [  ElevatedButton(  onPressed: _isBeforeToday() ? null : _prevMonth,  child: const Icon(Icons.keyboard_arrow_left_rounded),  key: const Key("DECREMENT_MONTH"),  ),  Container(  width: MediaQuery.of(context).size.width * 0.6,  alignment: Alignment.center,  child: FittedBox(  fit: BoxFit.fitWidth,  child: Text(  DateFormat.yMMMM().format(DateTime(year, month)),  style: _theme.textTheme.headline6,  ),  ),  ),  ElevatedButton(  onPressed: _isPastMaxDate() ? null : _nextMonth,  child: const Icon(Icons.keyboard_arrow_right_rounded),  key: const Key("INCREMENT_MONTH"),  ),  ],  );  } }  

This is the test that I have written to test that the months are increasing as expected when you tap on the increment month ElevatedButton. However, the test fails. When I adjust the test to test for thisMonth.year, thisMonth.month, it passes. This suggests to me that tester.tap has not changed the text or there is no «rebuild» happening. Why is that? I have tried to add longer durations to tester.pump / tester.pumpAndSettle but it does not appear to be working.

 //home_screen_test.dart  void main() {  var thisMonth = DateTime(DateTime.now().year, DateTime.now().month, 1);  var nextMonthTemp = thisMonth.add(const Duration(days: 35));  var nextMonth = DateTime(nextMonthTemp.year, nextMonthTemp.month, 1);  var prevMonthTemp = thisMonth.subtract(const Duration(days: 2));  var prevMonth = DateTime(prevMonthTemp.year, prevMonthTemp.month, 1);  var maxDate = thisMonth.add(const Duration(days: 100));   Widget createWidgetForTesting({required Widget child}) {  return MaterialApp(  home: child,  );  }   testWidgets('MonthController should increment month',  (WidgetTester tester) async {  await tester.pumpWidget(  createWidgetForTesting(  child: HomeScreen(),  ),  );  await tester.pumpAndSettle();  await tester.tap(find.byKey(const Key("INCREMENT_MONTH")));  await tester.pumpAndSettle();  expect(  find.text(  DateFormat.yMMMM().format(  DateTime(nextMonth.year, nextMonth.month),  ),  ),  findsOneWidget);  }); }  

Error message:

 ══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════ The following TestFailure object was thrown running a test:  Expected: exactly one matching node in the widget tree  Actual: _TextFinder:lt;zero widgets with text "November 2021" (ignoring offstage widgets)gt;  Which: means none were found but one was expected  When the exception was thrown, this was the stack: #4 main.lt;anonymous closuregt; (file:///....../test/home_screen_test.dart:47:5) lt;asynchronous suspensiongt; lt;asynchronous suspensiongt; (elided one frame from package:stack_trace)  This was caught by the test expectation on the following line:  file:///....../test/home_screen_test.dart line 47 The test description was:  MonthController should increment month  

Отдельно я попытался написать тест для month_controller_test.вместо этого дарт. Чтобы тест работал, мне пришлось изменить month_controller.dart на виджет с отслеживанием состояния. Хотя это, вероятно, нормально, я все же хотел бы понять, почему вышеперечисленное не сработало.