https://iiro.dev/writing-widget-tests-for-navigation-events/
本文介绍如何进行小部件测试以测试目标小部件。
我参考这篇文章在使用提供程序时测试转换目标小部件。结果,当我在过渡源中使用 Cosumer 时,我能够正确显示它。但是,在本文中,转换目标不会重建小部件,因此出现以下错误。
═╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following ProviderNotFoundException was thrown building Consumer<QuestionState>(dirty):
Error: Could not find the correct Provider<QuestionState> above this Consumer<QuestionState> Widget
This happens because you used a `BuildContext` that does not include the provider
of your choice. There are a few common scenarios:
- You added a new provider in your `main.dart` and performed a hot-reload.
To fix, perform a hot-restart.
- The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then
other routes will not be able to access that provider.
- You used a `BuildContext` that is an ancestor of the provider you are trying to read.
Make sure that Consumer<QuestionState> is under your MultiProvider/Provider<QuestionState>.
This usually happens when you are creating a provider and trying to read it immediately.
For example, instead of:
```
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// Will throw a ProviderNotFoundError, because `context` is associated
// to the widget that is the parent of `Provider<Example>`
child: Text(context.watch<Example>()),
),
}
```
consider using `builder` like so:
```
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// we use `builder` to obtain a new `BuildContext` that has access to the provider
builder: (context) {
// No longer throws
return Text(context.watch<Example>()),
}
),
}
```
If none of these solutions work, consider asking for help on StackOverflow:
https://stackoverflow.com/questions/tagged/flutter
The relevant error-causing widget was:
Consumer<QuestionState>
file:///Users/yamamotohiroto/dev_workspace/every-questions/frontend/lib/main2.dart:41:13
When the exception was thrown, this was the stack:
#0 Provider._inheritedElementOf (package:provider/src/provider.dart:332:7)
#1 Provider.of (package:provider/src/provider.dart:284:30)
#2 Consumer.buildWithChild (package:provider/src/consumer.dart:182:16)
#3 SingleChildStatelessWidget.build (package:nested/nested.dart:259:41)
#4 StatelessElement.build (package:flutter/src/widgets/framework.dart:4648:28)
#5 SingleChildStatelessElement.build (package:nested/nested.dart:279:18)
#6 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4574:15)
#7 Element.rebuild (package:flutter/src/widgets/framework.dart:4267:5)
#8 ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:4553:5)
#9 ComponentElement.mount (package:flutter/src/widgets/framework.dart:4548:5)
#10 SingleChildWidgetElementMixin.mount (package:nested/nested.dart:222:11)
... Normal element mounting (19 frames)
#29 Element.inflateWidget (package:flutter/src/widgets/framework.dart:3611:14)
#30 MultiChildRenderObjectElement.inflateWidget (package:flutter/src/widgets/framework.dart:6221:36)
#31 MultiChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:6232:32)
... Normal element mounting (236 frames)
#267 Element.inflateWidget (package:flutter/src/widgets/framework.dart:3611:14)
#268 MultiChildRenderObjectElement.inflateWidget (package:flutter/src/widgets/framework.dart:6221:36)
#269 Element.updateChild (package:flutter/src/widgets/framework.dart:3363:18)
#270 RenderObjectElement.updateChildren (package:flutter/src/widgets/framework.dart:5654:32)
#271 MultiChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6243:17)
#272 Element.updateChild (package:flutter/src/widgets/framework.dart:3350:15)
#273 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4599:16)
#274 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4746:11)
#275 Element.rebuild (package:flutter/src/widgets/framework.dart:4267:5)
#276 StatefulElement.update (package:flutter/src/widgets/framework.dart:4778:5)
#277 Element.updateChild (package:flutter/src/widgets/framework.dart:3350:15)
#278 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4599:16)
#279 Element.rebuild (package:flutter/src/widgets/framework.dart:4267:5)
#280 ProxyElement.update (package:flutter/src/widgets/framework.dart:4922:5)
#281 Element.updateChild (package:flutter/src/widgets/framework.dart:3350:15)
#282 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4599:16)
#283 Element.rebuild (package:flutter/src/widgets/framework.dart:4267:5)
#284 ProxyElement.update (package:flutter/src/widgets/framework.dart:4922:5)
#285 _InheritedNotifierElement.update (package:flutter/src/widgets/inherited_notifier.dart:181:11)
#286 Element.updateChild (package:flutter/src/widgets/framework.dart:3350:15)
#287 SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6090:14)
#288 Element.updateChild (package:flutter/src/widgets/framework.dart:3350:15)
#289 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4599:16)
#290 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4746:11)
#291 Element.rebuild (package:flutter/src/widgets/framework.dart:4267:5)
#292 StatefulElement.update (package:flutter/src/widgets/framework.dart:4778:5)
#293 Element.updateChild (package:flutter/src/widgets/framework.dart:3350:15)
#294 SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6090:14)
#295 Element.updateChild (package:flutter/src/widgets/framework.dart:3350:15)
#296 SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6090:14)
#297 Element.updateChild (package:flutter/src/widgets/framework.dart:3350:15)
#298 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4599:16)
#299 Element.rebuild (package:flutter/src/widgets/framework.dart:4267:5)
#300 ProxyElement.update (package:flutter/src/widgets/framework.dart:4922:5)
#301 Element.updateChild (package:flutter/src/widgets/framework.dart:3350:15)
#302 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4599:16)
#303 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4746:11)
#304 Element.rebuild (package:flutter/src/widgets/framework.dart:4267:5)
#305 BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2582:33)
#306 AutomatedTestWidgetsFlutterBinding.drawFrame (package:flutter_test/src/binding.dart:1106:19)
#307 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:328:5)
#308 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1144:15)
#309 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1082:9)
#310 AutomatedTestWidgetsFlutterBinding.pump.<anonymous closure> (package:flutter_test/src/binding.dart:973:9)
#313 TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:71:41)
#314 AutomatedTestWidgetsFlutterBinding.pump (package:flutter_test/src/binding.dart:960:27)
#315 WidgetTester.pumpAndSettle.<anonymous closure> (package:flutter_test/src/widget_tester.dart:652:23)
#316 WidgetTester.pumpAndSettle.<anonymous closure> (package:flutter_test/src/widget_tester.dart:646:38)
#319 TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:71:41)
#320 WidgetTester.pumpAndSettle (package:flutter_test/src/widget_tester.dart:646:27)
#321 main.<anonymous closure>._navigateToDetailsPage (file:///Users/yamamotohiroto/dev_workspace/every-questions/frontend/test/navigation2_test.dart:39:20)
<asynchronous suspension>
<asynchronous suspension>
(elided 5 frames from dart:async and package:stack_trace)
═══════════════════════════════════════════════════════════════════════════════════════════════════
══╡ 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:<zero widgets with text "Hello!" (ignoring offstage widgets)>
Which: means none were found but one was expected
When the exception was thrown, this was the stack:
#4 main.<anonymous closure>.<anonymous closure> (file:///Users/yamamotohiroto/dev_workspace/every-questions/frontend/test/navigation2_test.dart:64:7)
<asynchronous suspension>
<asynchronous suspension>
(elided one frame from package:stack_trace)
...
This was caught by the test expectation on the following line:
file:///Users/yamamotohiroto/dev_workspace/every-questions/frontend/test/navigation2_test.dart line 64
The test description was:
when tapping "navigate to details" button, should navigate to details page
════════════════════════════════════════════════════════════════════════════════════════════════════
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following message was thrown:
Multiple exceptions (2) were detected during the running of the current test, and at least one was
unexpected.
════════════════════════════════════════════════════════════════════════════════════════════════════
Test failed. See exception logs above.
The test description was: when tapping "navigate to details" button, should navigate to details page
✖ MainPage navigation tests when tapping "navigate to details" button, should navigate to details page
Exited (1)
════════════════════════════════════════════════════════════════════════════════════════════════════
应用代码
import 'package:flutter/material.dart';
import 'package:frontend/ViewModel/question_state.dart';
import 'package:provider/provider.dart';
class MainPage extends StatelessWidget {
static const navigateToDetailsButtonKey = Key('navigateToDetails');
void _navigateToDetailsPage(BuildContext context) {
final route = MaterialPageRoute(builder: (_) => DetailsPage('Hello!'));
Navigator.of(context).push(route);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Testing navigation'),
),
body: Consumer<QuestionState>(
builder: (context, model,_) {
return RaisedButton(
key: navigateToDetailsButtonKey,
onPressed: () => _navigateToDetailsPage(context),
child: Text('Navigate to details page!'),
);
}
),
);
}
}
class DetailsPage extends StatelessWidget {
DetailsPage(this.message);
final String message;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Details page'),
),
body: Consumer<QuestionState>(
builder: (context, model,_) {
return Center(
child: Text(message),
);
}
),
);
}
}
测试代码
import 'package:flutter_test/flutter_test.dart';
import 'package:frontend/ViewModel/question_state.dart';
import 'package:frontend/main2.dart';
import 'package:mockito/mockito.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class MockNavigatorObserver extends Mock implements NavigatorObserver {}
void main() {
group('MainPage navigation tests', () {
late NavigatorObserver mockObserver;
setUp(() {
mockObserver = MockNavigatorObserver();
});
Future<void> _buildMainPage(WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
home: ChangeNotifierProvider<QuestionState>(
create: (context) => QuestionState(), child: MainPage()),
// This mocked observer will now receive all navigation events
// that happen in our app.
navigatorObservers: [mockObserver],
));
// The tester.pumpWidget() call above just built our app widget
// and triggered the pushObserver method on the mockObserver once.
// verify(mockObserver.didPush(any, any));
}
Future<void> _navigateToDetailsPage(WidgetTester tester) async {
// Tap the button which should navigate to the details page.
//
// By calling tester.pumpAndSettle(), we ensure that all animations
// have completed before we continue further.
await tester.tap(find.byKey(MainPage.navigateToDetailsButtonKey));
await tester.pumpAndSettle();
}
testWidgets(
'when tapping "navigate to details" button, should navigate to details page',
(WidgetTester tester) async {
// TODO: Write the test case here.
await _buildMainPage(tester);
await _navigateToDetailsPage(tester);
// By tapping the button, we should've now navigated to the details
// page. The didPush() method should've been called...
// verify(mockObserver.didPush(any!, any));
// ...and there should be a DetailsPage present in the widget tree...
expect(find.byType(DetailsPage), findsOneWidget);
// ...with the message we sent from main page.
expect(find.text('Hello!'), findsOneWidget);
});
});
}
我尝试的方法是在 await _navigateToDetailsPage(tester); 之后在测试代码的下一行强制重建 DetailPage 小部件;
// ....
await _navigateToDetailsPage(tester);
await tester.pumpWidget(MaterialApp(
home: ChangeNotifierProvider<QuestionState>(
create: (context) => QuestionState(), child: DetailsPage('Hello')),
// This mocked observer will now receive all navigation events
// that happen in our app.
navigatorObservers: [mockObserver],
));
// ....
但是,我得到了同样的错误。
我应该怎么办?