如何在 Flutter 中测试在 didUpdateWidget() 中实例化的 widget?

Mar*_*ary 6 testing asynchronous widget flutter

我有一个 StatefulWidget,它AnimatedCrossFade在 中创建一个小部件didUpdateWidget(),并将其另存为animation. 这是一个简化的示例:

class BackgroundImage extends StatefulWidget {
  final Color colorA;
  final Color colorB;

  const BackgroundImage({
      this.colorA,
      this.colorB,
  });
}

class _BackgroundImageState extends State<BackgroundImage> {
  Widget _animation;

  @override
  void didUpdateWidget(Widget old) {
    super.didUpdateWidget(old);
    _buildBackgroundA(colorA).then((backgroundA) {
      _buildBackgroundB(colorB).then(backgroundB) {
        print(backgroundA); // this is not null
        print(backgroundB); // this is not null
        _animation = AnimatedCrossFade(
          duration: Duration(seconds: 15),
          firstChild: backgroundA,
          secondChild: backgroundB,
          crossFadeState: someVarToSwitchColors,
              ? CrossFadeState.showFirst
              : CrossFadeState.showSecond,
          );
       }
     }
  }

  @override
  Widget build(BuildContext context) {
    return _animation != null ? _animation : Container();
  }
}
Run Code Online (Sandbox Code Playgroud)

_buildBackgroundA()_buildBackgroundB()是返回 的异步函数Future<Widget>。这在我的应用程序中运行良好 -didUpdateWidget()被称为,我的AnimatedCrossFade显示并在两个背景之间动画。

AnimatedCrossFade但是,我在测试中找不到。我能够找到我的其他无状态小部件,也能够找到该BackgroundImage小部件。我有类似的东西:

    await tester.pump();
    await tester.pumpAndSettle(Duration(minutes: 1));
    expect(
        find.descendant(
            of: find.byType(BackgroundImage),
            matching: find.byType(AnimatedCrossFade)),
        findsOneWidget);
Run Code Online (Sandbox Code Playgroud)

这会失败,因为它找不到AnimatedCrossFade.

如果我将我的build()功能更改为:

  @override
  Widget build(BuildContext context) {
    return AnimatedCrossFade(...);
  }
Run Code Online (Sandbox Code Playgroud)

我能够找到我的小部件。所以我怀疑这与我的功能完成expect之前执行的测试有关。_buildBackground()我尝试过改变我的持续时间pump,但pumpAndSettle没有效果。如何强制测试等待更多时间?我还缺少其他东西吗?

测试日志如下所示(带有我的打印结果):

Running Flutter tests.
00:00 +0: ...d/work/.../cl00:00 +0: (setUpAll)                                                                                                          00:00 +0: Test background                                              
init state called
_buildBackgroundA called
init state called
_buildBackgroundB called
...
00:00 +0 -1: Test background
    Expected: exactly one matching node in the widget tree
    Actual: ?:<zero widgets with type "AnimatedCrossFade" that has ancestor(s) with type "BackgroundImage" (ignoring offstage widgets)>
     Which: means none were found but one was expected
  This was caught by the test expectation on the following line:
 ...line 179

about to return from calling _buildBackgroundA
image: Image(image: MemoryImage(_Uint8ArrayView#af55b, scale: 1.0), width: 50.0, height: 200.0, fit: cover, alignment: center, this.excludeFromSemantics: false, filterQuality: low)
about to return from calling _buildBackgroundB
image: Image(image: MemoryImage(_Uint8ArrayView#79291, scale: 1.0), width: 50.0, height: 830.0, fit: cover, alignment: center, this.excludeFromSemantics: false, filterQuality: low)
...

Run Code Online (Sandbox Code Playgroud)

小智 1

我们不需要didUpdateWidget作为单独的方法进行测试。所以,首先我们要知道flutter里面什么时候didUpdateWidget会被调用。

  1. 当小部件的父级重建时:如果小部件的父级由于某些更改而重建,框架也会重建该小部件。这将触发didUpdateWidget使用新小部件调用的方法。

  2. 当使用新参数重建小部件时:如果使用新参数重建 StatefulWidget,框架将调用 didUpdateWidget 方法。这是因为小部件已更改,并且可能需要更新其内部状态以反映新参数。

  3. 当使用相同的参数重建widget时:如果使用相同的参数重建StatefulWidget,框架将调用该didUpdateWidget方法。这是因为小部件已更改,即使参数相同,它也可能需要更新其内部状态。

因此,如果您正在测试一个在测试时没有父级的小部件。用 StatefulBuilder 包装它,并使用任何方法更改传递给小部件的变量,以测试它didUpdateWidget是否正常工作。

我将举一个例子来说明如何在代码中执行此操作。

      await tester.pumpWidget(
        MaterialApp(
          theme: AppThemes.theme(themeMode: ThemeMode.light),
          home: Scaffold(
            body: StatefulBuilder(
              builder: (context,setState) {
                return GiftWrap(
                  title: titleString,
                  subTitle: "You Have 0 Loyalty Point.",
                  icon: Icons.info_outline,
                  iconLabel: "Redeem Now",
                  iconPath: loyalPointIconPath,
                  onTap: (redeem) {
                    setState((){
                      titleString = "Loyalty";
                    });
                  },
                );
              }
            ),
          ),
        ),
      );
Run Code Online (Sandbox Code Playgroud)

如果还有什么疑问的话。那就在下面评论吧。