У меня есть тест, в котором я пытаюсь наблюдать за поведением [Navigator]
, когда приложение переходит от contacts_footer.dart
к create_and_edit_contact.dart
(push) и обратно (pop). Используя verify
из пакета Mockito, я могу успешно проверить, что поведение push работает, однако проверить поведение 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/
_buildMainPage
методе вы создаете вложенный виджетMaterialApp
вокруг виджетаContactsFooter
. Вы пытались удалить это? - person Jordan Davies   schedule 07.01.2020MaterialApp
необходим дляScaffold
, который обеспечивает правильный контекст, а удаление внутреннегоMaterialApp
приводит кProvider<Contacts> not found error
. Однако я думаю, что нашел решение, переместивnavigatorObservers
на внутреннийMaterialApp
. Кроме того, мне нужно было убедиться, что событие push возникло сразу после_buildMainPage
, но до_navigateToDetailsPage
. Попсовое поведение теперь наблюдается правильно. - person Calvin Gonsalves   schedule 07.01.2020