FutureBuilder 构建的小部件可以在应用程序中工作,但从未在小部件测试下创建

问题描述 投票:0回答:1

在探索 flutter 时,我尝试创建一个小部件,它从内存中渲染图像(至少目前如此)并在其上方显示

IconButton
。要校正
IconButton
的大小,我需要获取图像的实际大小。因此,要设置
iconSize
,我将
FutureBuilder
Completer
Image.resolve(...).addListener
结合使用。

它按预期工作。但我很难让小部件测试通过,因为它从不渲染图像(在加载图像时,测试图像数据的期望通过)并且从不创建

IconButton
小部件,所以我的检查没有通过。

这应该是

testWidgets
使用的 FakeAsync 的东西。
tester.pump
应该进步假时间,但即使我在一个循环中运行
pump
IconButton
小部件也永远不会出现。可以使用 ImageStream b

class Thumbnail extends StatelessWidget {
  final Uint8List thumbnail;
  final ImagePage mainImage;
  final Image thumbnailImage;
  final bool editMode;
  final completer = Completer<ui.Image>();

  Thumbnail(
      {super.key,
      required this.thumbnail,
      required this.mainImage,
      this.editMode = false})
      : thumbnailImage = Image.memory(thumbnail);

  @override
  Widget build(BuildContext context) {
    thumbnailImage.image
        .resolve(const ImageConfiguration())
        .addListener(ImageStreamListener((image, synchronousCall) {
      completer.complete(image.image);
      image.dispose();
    }));
    return Stack(alignment: Alignment.center, children: [
      thumbnailImage,
      FutureBuilder<ui.Image>(
          future: completer.future,
          builder: (context, snapshot) {
            return Offstage(
                offstage: !snapshot.hasData,
                child: makeIconButton(context, snapshot.data));
          }),
    ]);
  }

  static int minSize(ui.Image? image) {
    if (image == null) {
      return 2;
    }
    return image.width < image.height ? image.width : image.height;
  }

  Widget makeIconButton(BuildContext context, ui.Image? image) {
    final imageMinSize = minSize(image);
    return IconButton(
      onPressed: () {
        if (!editMode) {
          Navigator.of(context).push(MaterialPageRoute(
              builder: (context) => page.Page.fromWidget(mainImage)));
        }
      },
      icon: editMode ? const Icon(Icons.edit) : const Icon(Icons.zoom_in),
      iconSize: imageMinSize >= 64 ? 48.0 : imageMinSize / 2,
      splashRadius: imageMinSize / 2,
      color: Colors.blueGrey.withOpacity(0.8),
      hoverColor: Colors.lightBlue.withOpacity(0.3),
    );
  }
}

测试代码为:

void main() {
  testWidgets('Thumbnail', (WidgetTester tester) async {
      var thumbnailPage = page.Page('Thumbnail', const [],
          thumbnail_package.Thumbnail(
              thumbnail: image_samples.thumbnail_64x64,
              mainImage: thumbnail_package.ImagePage(
                  'MainImage',
                  MemoryImage(image_samples.thumbnail_64x64)
              )
          ));
      await tester.pumpWidget(
          MaterialApp(home: thumbnailPage)
      );

      expect(find.byType(thumbnail_package.Thumbnail), findsOneWidget);

      expect(find.byType(Image), findsOneWidget);
      for (final image in find.byType(Image).evaluate().map((element) =>
      element.widget as Image)) {
        expect(
            Uint8ListCompareByValue((image.image as MemoryImage).bytes),
            Uint8ListCompareByValue(image_samples.thumbnail_64x64));
      }

      // while (!find
      //     .byType(IconButton)
      //     .hasFound) {
      //   await tester.pump();
      // }

      await tester.tap(find.byType(IconButton));  // <--- THIS CALL FAILS
      await tester.pumpAndSettle();

      expect(find.byType(thumbnail_package.Thumbnail), findsNothing);
      expect(find.byType(Image), findsOneWidget);
      for (final image in find.byType(Image).evaluate().map((element) =>
      element.widget as Image)) {
        expect(
            Uint8ListCompareByValue((image.image as MemoryImage).bytes),
            Uint8ListCompareByValue(image_samples.thumbnail_64x64));
      }
  });

我收到以下错误:

运行测试时抛出以下断言: 查找器“找到 0 个类型为“IconButton”的小部件:[]”(用于调用“tap()”)找不到任何匹配的小部件。

您能给我关于如何在测试中处理这种情况的任何建议吗?

flutter flutter-futurebuilder
1个回答
0
投票

好吧,问题是我对异步调用如何工作的错误期望:不要期望任何特殊的执行顺序。

在模拟器上的调试运行和首先调用 ImageStreamListener 的测试运行中,对 ImageStreamListener 和 FutureBuilder 的调用顺序不同。结果,FutureBuilder 是在已经完成的 future 中创建的。

FutureBuilder 本身的设计(对我来说有些奇怪的设计),这样如果 future 在第一次调用时准备就绪,它就不会第二次调用它的构建器回调。但在第一次调用时,它会传递等待状态和空快照。所以我们在这里,要么确保稍后准备好,要么检查未来而不是快照变量(如果您确定它已准备好)。

此代码工作正常,因为我在创建 FutureBuilder 后强制调用回调:

  @override
  Widget build(BuildContext context) {
    final widget = Stack(alignment: Alignment.center, children: [
      thumbnailImage,
      FutureBuilder<ui.Image>(
          future: completer.future,
          builder: (context, snapshot) {
            return Offstage(
                offstage: !snapshot.hasData,
                child: makeIconButton(context, snapshot.data));
          }),
    ]);
    thumbnailImage.image
        .resolve(const ImageConfiguration())
        .addListener(ImageStreamListener((image, synchronousCall) {
      completer.complete(image.image);
      image.dispose();
    }));
    return widget;
  }

在水龙头前简单添加一个泵并沉淀:

  await tester.pumpAndSettle();  // additional call to process async callback and rebuild widget
  await tester.tap(find.byType(IconButton));
  await tester.pumpAndSettle();

虽然我不完全确定这是否是一个稳定的解决方案,并且在发生更多异步调用的情况下它不会失败。

© www.soinside.com 2019 - 2024. All rights reserved.