首页 > 解决方案 > 当 pumpWidget 为 ChangeNotifierProvider 时测试 Widget 抛出错误

问题描述

我正在通过这个codelab学习 Flutter Widget Test ,第一个示例抛出了一个我无法修复的错误:/,我正在尝试进行这个测试:

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import '../lib/models/favorites.dart';
import '../lib/screens/home.dart';

Widget createHomeScreen() {
  return ChangeNotifierProvider<Favorites>(
    create: (context) => Favorites(),
    child: MaterialApp(
      home: HomePage(),
    ),
  );
}

void main() {
  group(
    'Home Page Widget Tests',
    () {
      testWidgets(
        'Testing Scrolling',
        (tester) async {
          await tester.pumpWidget(createHomeScreen());

          expect(find.text('Item 0'), findsOneWidget);

          await tester.fling(find.byType(ListView), Offset(0, -200), 3000);
          await tester.pumpAndSettle();

          expect(find.text('Item 0'), findsNothing);
        },
      );
    },
  );
}

home.dart 和 favorites.dart 代码:

主页.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:testing_app/models/favorites.dart';
import 'package:testing_app/screens/favorites.dart';

class HomePage extends StatelessWidget {
  static String routeName = '/';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Testing Sample'),
        actions: <Widget>[
          TextButton.icon(
            style: TextButton.styleFrom(primary: Colors.white),
            onPressed: () {
              Navigator.pushNamed(context, FavoritesPage.routeName);
            },
            icon: Icon(Icons.favorite_border),
            label: Text('Favorites'),
          ),
        ],
      ),
      body: ListView.builder(
        itemCount: 100,
        cacheExtent: 20.0,
        padding: const EdgeInsets.symmetric(vertical: 16),
        itemBuilder: (context, index) => ItemTile(index),
      ),
    );
  }
}

class ItemTile extends StatelessWidget {
  final int itemNo;

  const ItemTile(
    this.itemNo,
  );

  @override
  Widget build(BuildContext context) {
    var favoritesList = Provider.of<Favorites>(context);

    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: ListTile(
        leading: CircleAvatar(
          backgroundColor: Colors.primaries[itemNo % Colors.primaries.length],
        ),
        title: Text(
          'Item $itemNo',
          key: Key('text_$itemNo'),
        ),
        trailing: IconButton(
          key: Key('icon_$itemNo'),
          icon: favoritesList.items.contains(itemNo)
              ? Icon(Icons.favorite)
              : Icon(Icons.favorite_border),
          onPressed: () {
            !favoritesList.items.contains(itemNo)
                ? favoritesList.add(itemNo)
                : favoritesList.remove(itemNo);
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(
                content: Text(favoritesList.items.contains(itemNo)
                    ? 'Added to favorites.'
                    : 'Removed from favorites.'),
                duration: Duration(seconds: 1),
              ),
            );
          },
        ),
      ),
    );
  }
}

收藏夹.dart

import 'package:flutter/material.dart';

/// The [Favorites] class holds a list of favorite items saved by the user.
class Favorites extends ChangeNotifier {
  final List<int> _favoriteItems = [];

  List<int> get items => _favoriteItems;

  void add(int itemNo) {
    _favoriteItems.add(itemNo);
    notifyListeners();
  }

  void remove(int itemNo) {
    _favoriteItems.remove(itemNo);
    notifyListeners();
  }
}

出现的错误是:

00:03 +0: Home Page Widget Tests Testing Scrolling                                                                                  
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following ProviderNotFoundException was thrown building ItemTile(dirty):
Error: Could not find the correct Provider<Favorites> above this ItemTile 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 ItemTile is under your MultiProvider/Provider<Favorites>.
  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:
  ItemTile file:///home/avatar/Documents/Flutter%20projects/testing_app/lib/screens/home.dart:29:42

When the exception was thrown, this was the stack:
#0      Provider._inheritedElementOf (package:provider/src/provider.dart:329:7)
#1      Provider.of (package:provider/src/provider.dart:281:30)
#2      ItemTile.build (file:///home/avatar/Documents/Flutter%20projects/testing_app/lib/screens/home.dart:44:34)
#3      StatelessElement.build (package:flutter/src/widgets/framework.dart:4569:28)
#4      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4495:15)
#5      Element.rebuild (package:flutter/src/widgets/framework.dart:4189:5)
#6      ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:4474:5)
#7      ComponentElement.mount (package:flutter/src/widgets/framework.dart:4469:5)
...     Normal element mounting (33 frames)
#40     Element.inflateWidget (package:flutter/src/widgets/framework.dart:3541:14)
#41     Element.updateChild (package:flutter/src/widgets/framework.dart:3306:18)
#42     SliverMultiBoxAdaptorElement.updateChild (package:flutter/src/widgets/sliver.dart:1229:37)
#43     SliverMultiBoxAdaptorElement.createChild.<anonymous closure> (package:flutter/src/widgets/sliver.dart:1214:20)
#44     BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2647:19)
#45     SliverMultiBoxAdaptorElement.createChild (package:flutter/src/widgets/sliver.dart:1207:12)
#46     RenderSliverMultiBoxAdaptor._createOrObtainChild.<anonymous closure> (package:flutter/src/rendering/sliver_multi_box_adaptor.dart:349:23)
#47     RenderObject.invokeLayoutCallback.<anonymous closure> (package:flutter/src/rendering/object.dart:1894:59)
#48     PipelineOwner._enableMutationsToDirtySubtrees (package:flutter/src/rendering/object.dart:915:15)
#49     RenderObject.invokeLayoutCallback (package:flutter/src/rendering/object.dart:1894:14)
#50     RenderSliverMultiBoxAdaptor._createOrObtainChild (package:flutter/src/rendering/sliver_multi_box_adaptor.dart:338:5)
#51     RenderSliverMultiBoxAdaptor.addInitialChild (package:flutter/src/rendering/sliver_multi_box_adaptor.dart:422:5)
#52     RenderSliverList.performLayout (package:flutter/src/rendering/sliver_list.dart:79:12)
#53     RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#54     RenderSliverEdgeInsetsPadding.performLayout (package:flutter/src/rendering/sliver_padding.dart:137:12)
#55     RenderSliverPadding.performLayout (package:flutter/src/rendering/sliver_padding.dart:371:11)
#56     RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#57     RenderViewportBase.layoutChildSequence (package:flutter/src/rendering/viewport.dart:512:13)
#58     RenderViewport._attemptLayout (package:flutter/src/rendering/viewport.dart:1570:12)
#59     RenderViewport.performLayout (package:flutter/src/rendering/viewport.dart:1479:20)
#60     RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#61     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118:14)
#62     RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#63     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118:14)
#64     RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#65     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118:14)
#66     RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#67     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118:14)
#68     RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#69     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118:14)
#70     RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#71     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118:14)
#72     RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#73     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118:14)
#74     RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#75     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118:14)
#76     RenderCustomPaint.performLayout (package:flutter/src/rendering/custom_paint.dart:546:11)
#77     RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#78     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118:14)
#79     RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#80     MultiChildLayoutDelegate.layoutChild (package:flutter/src/rendering/custom_layout.dart:171:12)
#81     _ScaffoldLayout.performLayout (package:flutter/src/material/scaffold.dart:925:7)
#82     MultiChildLayoutDelegate._callPerformLayout (package:flutter/src/rendering/custom_layout.dart:243:7)
#83     RenderCustomMultiChildLayoutBox.performLayout (package:flutter/src/rendering/custom_layout.dart:407:14)
#84     RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#85     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118:14)
#86     RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#87     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118:14)
#88     _RenderCustomClip.performLayout (package:flutter/src/rendering/proxy_box.dart:1371:11)
#89     RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#90     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118:14)
#91     RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#92     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118:14)
#93     RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#94     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118:14)
#95     RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#96     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118:14)
#97     RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#98     RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118:14)
#99     RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#100    RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118:14)
#101    RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#102    RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118:14)
#103    RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#104    RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118:14)
#105    RenderOffstage.performLayout (package:flutter/src/rendering/proxy_box.dart:3362:13)
#106    RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#107    RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118:14)
#108    RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#109    _RenderTheatre.performLayout (package:flutter/src/widgets/overlay.dart:743:15)
#110    RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#111    RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118:14)
#112    RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#113    RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118:14)
#114    RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#115    RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118:14)
#116    RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#117    RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118:14)
#118    RenderCustomPaint.performLayout (package:flutter/src/rendering/custom_paint.dart:546:11)
#119    RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#120    RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118:14)
#121    RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#122    RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:118:14)
#123    RenderObject.layout (package:flutter/src/rendering/object.dart:1784:7)
#124    RenderView.performLayout (package:flutter/src/rendering/view.dart:153:14)
#125    RenderObject._layoutWithoutResize (package:flutter/src/rendering/object.dart:1641:7)
#126    PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:884:18)
#127    AutomatedTestWidgetsFlutterBinding.drawFrame (package:flutter_test/src/binding.dart:1105:23)
#128    RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:319:5)
#129    SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1144:15)
#130    SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1082:9)
#131    AutomatedTestWidgetsFlutterBinding.pump.<anonymous closure> (package:flutter_test/src/binding.dart:969:9)
#134    TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:72:41)
#135    AutomatedTestWidgetsFlutterBinding.pump (package:flutter_test/src/binding.dart:956:27)
#136    WidgetTester.pumpWidget.<anonymous closure> (package:flutter_test/src/widget_tester.dart:522:22)
#139    TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:72:41)
#140    WidgetTester.pumpWidget (package:flutter_test/src/widget_tester.dart:519:27)
#141    main.<anonymous closure>.<anonymous closure> (file:///home/avatar/Documents/Flutter%20projects/testing_app/test/home_test.dart:23:24)
#142    main.<anonymous closure>.<anonymous closure> (file:///home/avatar/Documents/Flutter%20projects/testing_app/test/home_test.dart:22:9)
#143    testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart:144:29)
<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 "Item 0" (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:///home/avatar/Documents/Flutter%20projects/testing_app/test/home_test.dart:25:11)
<asynchronous suspension>
<asynchronous suspension>
(elided one frame from package:stack_trace)
...

This was caught by the test expectation on the following line:
  file:///home/avatar/Documents/Flutter%20projects/testing_app/test/home_test.dart line 25
The test description was:
  Testing Scrolling
════════════════════════════════════════════════════════════════════════════════════════════════════
══╡ 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.
════════════════════════════════════════════════════════════════════════════════════════════════════
00:03 +0 -1: Home Page Widget Tests Testing Scrolling [E]                                                                           
  Test failed. See exception logs above.
  The test description was: Testing Scrolling
  
00:03 +0 -1: Some tests failed.

我已经尝试在 MaterialAnd() 和 ChangueNotifierProvier() 周围放置一个 Builder() 但没有成功 TT(对不起,我的英语很糟糕)

标签: flutterflutter-testflutter-provider

解决方案


推荐阅读