0

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],
          ));
    // ....

但是,我得到了同样的错误。

我应该怎么办?

4

0 回答 0