Как правильно смоделировать NavigatorObserver во флаттере с помощью Mockito? - PullRequest
0 голосов
/ 07 января 2020

У меня есть тест, в котором я пытаюсь наблюдать поведение [Navigator], когда приложение перемещается из contacts_footer.dart в create_and_edit_contact.dart (pu sh) и обратно (pop). Используя verify из пакета Mockito, я могу успешно проверить, что поведение pu sh работает, однако проверка поведения pop не удалась. Функция _navigateToBack работает, как и ожидалось, и тестирование для виджетов, которые появляются только в contacts_footer.dart, успешно, но наблюдать за всплывающим поведением не удается.

contacts_footer.dart

 class ContactsFooter extends StatelessWidget {
  static const navigateToEditPage = Key('navigateEdit');
  const ContactsFooter({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BottomAppBar(
      color: Color.fromRGBO(244, 244, 244, 1),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          IconButton(
            icon: Icon(Icons.edit),
            onPressed: () {
              Provider.of<Contacts>(context).setEditMode(true);
              Navigator.of(context).push(MaterialPageRoute(
                  builder: (context) => CreateAndEditContact()));
            },
            key: ContactsFooter.navigateToEditPage,
          )
        ],
      ),
    );
  }
}

create_and_edit_contact.dart


class CreateAndEditContact extends StatefulWidget {
  static const routeName = 'edit-contact';
  static var editOrCreateDetails = Key('editOrCreate');

  @override
  _CreateAndEditContactState createState() => _CreateAndEditContactState();
}

class _CreateAndEditContactState extends State<CreateAndEditContact> {
...
Widget build(BuildContext context) {
    _isEditMode = Provider.of<Contacts>(context).isEditMode;
...
return Scaffold (
..
RaisedButton(key: CreateAndEditContact.editOrCreateDetails,
             onPressed: () {
                   ....      
                            if (_isEditMode) {
                              print(true);
                              Provider.of<Contacts>(context)
                                  .updateContact(context,formData, _selectedContact.vUuid)
                                  .then((data) {
                                Navigator.of(context).pop();
                              });
                            } else {
                              print(false);
                              Provider.of<Contacts>(context)
                                  .createContact(context,formData)
                                  .then((data) {
                                Navigator.of(context).pop();
                              }).catchError(
                                (error) => showDialog(
                                  context: context,
                                  builder: (context) => ErrorDialog(
                                    error.toString(),
                                  ),
                                ),
                              );
                            }
                          },
                          child: Text(
                            'Sumbit',
                            style: TextStyle(color: Colors.white),
                          ),
                          color: Theme.of(context).accentColor,

                        ),
)

}

}

test file.

group('EditPage navigation tests', () {
    NavigatorObserver mockObserver;


    setUp(() {
      mockObserver = MockNavigatorObserver();

    });

    Future<Null> _buildMainPage(WidgetTester tester) async {
      await tester.pumpWidget(MaterialApp(
        home: Scaffold(
          body: Builder(
            builder: (context) => Center(
              child: MultiProvider(
      providers: [
        ChangeNotifierProvider<Contacts>(create: (_) => Contacts()),
      ],
      child: Builder(
        builder: (_) => MaterialApp(home: ContactsFooter(),
      ),
    );,
            ),
          ),
        ),

        /// This mocked observer will now receive all navigation events
        /// that happen in our app.
        navigatorObservers: <NavigatorObserver>[mockObserver],
      ));

      /// The tester.pumpWidget() call above just built our app widget
      /// and triggered the pushObserver method on the mockObserver once.


    }

    Future<Null> _navigateToDetailsPage(WidgetTester tester) async {
      /// Tap the button which should navigate to the edit details page.
      /// By calling tester.pumpAndSettle(), we ensure that all animations
      /// have completed before we continue further.

      await tester.tap(find.byKey(ContactsFooter.navigateToEditPage));
      await tester.pumpAndSettle();

    }

    Future<Null> _navigateToBack(WidgetTester tester) async {
      await tester.tap(find.byKey(CreateAndEditContact.editOrCreateDetails));
      int num = await tester.pumpAndSettle();
      print(num);
    }

    testWidgets(
        'when tapping "navigate to edit details" button, should navigate to details page',
        (WidgetTester tester) async {
      await _buildMainPage(tester);

      //CreateAndEditContact widget not present on screen as push event is not triggered yet
      expect(find.byType(CreateAndEditContact), findsNothing);
      //Trigger push event
      await _navigateToDetailsPage(tester);

      // By tapping the button, we should've now navigated to the edit details
      // page. The didPush() method should've been called...
      final Route pushedRoute =
          verify(mockObserver.didPush(captureAny, any)).captured.single;
      print(pushedRoute);


      // there should be a CreateAndEditContact page present in the widget tree...
      var createAndEdit = find.byType(CreateAndEditContact);
      expect(createAndEdit, findsOneWidget);



      await _navigateToBack(tester);

      verify(mockObserver.didPop(any, any));
      expect(find.byType(CreateAndEditContact), findsNothing);

      expect(find.byKey(ContactsFooter.navigateToEditPage), findsWidgets);


    });
}

Все ожидаемые операторы выполняются правильно. Однако verify(mockObserver.didPop(any, any)); приводит к исключению, как если бы [NavigatorObserver] не распознавал всплывающее поведение.

>(RouteSettings("/", null), animation: AnimationController#1a3c6(⏭ 1.000; paused; for MaterialPageRoute<dynamic>(/)))
5
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following TestFailure object was thrown running a test:
  No matching calls. All calls: MockNavigatorObserver.navigator,
MockNavigatorObserver._navigator==NavigatorState#36772(tickers: tracking 1 ticker), [VERIFIED]
MockNavigatorObserver.didPush(MaterialPageRoute<dynamic>(RouteSettings("/", null), animation:
AnimationController#1a3c6(⏭ 1.000; paused; for MaterialPageRoute<dynamic>(/))), null)
(If you called `verify(...).called(0);`, please instead use `verifyNever(...);`.)

When the exception was thrown, this was the stack:
#0      fail (package:test_api/src/frontend/expect.dart:153:30)
#1      _VerifyCall._checkWith (package:mockito/src/mock.dart:648:7)
#2      _makeVerify.<anonymous closure> (package:mockito/src/mock.dart:935:18)
#3      main.<anonymous closure>.<anonymous closure> (file:///Users/calvin.gonsalves/Projects/Flutter/Dec23-2019/cmic_mobile_field/test/main_widget_test.dart:316:13)
<asynchronous suspension>
#4      testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart:124:25)
#5      TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:696:19)
<asynchronous suspension>
#8      TestWidgetsFlutterBinding._runTest (package:flutter_test/src/binding.dart:679:14)
#9      AutomatedTestWidgetsFlutterBinding.runTest.<anonymous closure> (package:flutter_test/src/binding.dart:1050:24)
#15     AutomatedTestWidgetsFlutterBinding.runTest (package:flutter_test/src/binding.dart:1047:15)
#16     testWidgets.<anonymous closure> (package:flutter_test/src/widget_tester.dart:121:22)
#17     Declarer.test.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/declarer.dart:171:27)
<asynchronous suspension>
#18     Invoker.waitForOutstandingCallbacks.<anonymous closure> (package:test_api/src/backend/invoker.dart:242:15)
#23     Invoker.waitForOutstandingCallbacks (package:test_api/src/backend/invoker.dart:239:5)
#24     Declarer.test.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/declarer.dart:169:33)
#29     Declarer.test.<anonymous closure> (package:test_api/src/backend/declarer.dart:168:13)
#30     Invoker._onRun.<anonymous closure>.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/invoker.dart:392:25)
#44     _Timer._runTimers (dart:isolate-patch/timer_impl.dart:384:19)
#45     _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:418:5)
#46     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:174:12)
(elided 28 frames from class _FakeAsync, package dart:async, package dart:async-patch, and package stack_trace)

The test description was:
  when tapping "navigate to edit details" button, should navigate to details page
════════════════════════════════════════════════════════════════════════════════════════════════════
Test failed. See exception logs above.
The test description was: when tapping "navigate to edit details" button, should navigate to details page

✖ EditPage navigation tests when tapping "navigate to edit details" button, should navigate to details page

Я ссылался https://iirokrankka.com/2018/08/22/writing-widget-tests-for-navigation-events/

...