【问题标题】:How to test Flutter app where there is an async call in initState()?如何测试 initState() 中有异步调用的 Flutter 应用程序?
【发布时间】:2019-04-29 21:22:00
【问题描述】:

我有一个 StatefulWidget,它在其 initState() 中进行异步调用,以构建一个 Widget。当我手动运行它时,小部件会快速构建。

但是,在我的测试中,即使我使用 await tester.pump()await tester.pumpAndSettle(),小部件似乎也没有构建,直到测试运行之后。

小部件代码:

Widget _coolWidget;

@override
void initState() {
  super.initState();
  _coolWidget = Container(); // If I set this to OverflowBox() my test passes
  _buildWidgetFromCanvas();
}

Future<void> _buildWidgetFromCanvas() {
  final ui.PictureRecorder recorder = ui.PictureRecorder();
  final ui.Canvas canvas = ui.Canvas(recorder);
  // ... more canvas drawing code ...
  final img = ... // Image.memory() I build from the canvas
  if (!mounted) {
    print('no longer mounted');
    return;
  }

  setState(() {
    print(img);
    _coolWidget = OverflowBox(
      child: img,
    );
    print(_coolWidget);
  });
}

测试代码:

void main() {
  testWidgets('''OverflowBox shows up.''', (WidgetTester tester) async {
    await _setUp(tester); // Just instantiates my widget in an app
    await tester.pumpAndSettle();
    expect(find.byType(OverflowBox).evaluate().length, 1);
  });
}

我在以下位置运行测试结果时的输出:

failed: Error caught by Flutter test framework, thrown running a test.
Expected: <1>
Actual: <0>

但如果我在initState() 中设置_coolWidget = OverflowBox();,则测试通过。

我还有其他在此之后运行的测试。完成这些之后,我看到打印从上方记录了print(img);print(_coolWidget);,它正确记录了绘制的图像。

我也得到了 no longer mounted 打印,但这只是在 Flutter 内置 (tearDownAll) 之前的最后一次打印。

在 pump() 和 pumpAndSettle() 中设置持续时间似乎没有任何改变。

我可能遗漏了一些明显的东西。

【问题讨论】:

标签: asynchronous testing dart flutter


【解决方案1】:

如果您的 build 方法依赖于异步工作,请考虑使用 FutureBuilder


Future<Image> _buildWidgetFromCanvas() {
  final ui.PictureRecorder recorder = ui.PictureRecorder();
  final ui.Canvas canvas = ui.Canvas(recorder);
  // ... more canvas drawing code ...
  if (!mounted) {
    return null
  }
  final img = ... // Image.memory() I build from the canvas
  return img;
}

FutureBuilder<Image>(
  future: _buildWidgetFromCanvas, // a previously-obtained Future<Image> or null
  builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
    switch (snapshot.connectionState) {
      case ConnectionState.none:
      case ConnectionState.active:
      case ConnectionState.waiting:
        return SizedBox(height: <height>, width: <width>);
      case ConnectionState.done:
        return OverflowBox(child: snapshot.data);
    }
    return null; // unreachable
  },
)

你也可以像这样更新你的测试代码:

  expect(find.byType(OverflowBox), findsOneWidget);

【讨论】:

  • 我已将FutureBuilder&lt;Image&gt;(...) 代码放入我的initState() 中,因为我希望尽早构建小部件。从视觉上看,它的行为符合预期,但当我运行测试时,它似乎仍然找不到我的小部件。
  • 我认为您不希望 FutureBuilder 在您的 initstate 中。您可以尝试让您的 buildWidgetFromCanvas 缓存其最后一个值并从 initState 调用它。
  • 现在,在 initState() 我设置了一个变量,_savedFuture = _buildWidgetFromCanvas()。我已经将我的FutureBuilder 移动到 build() 中,我的future 属性现在是_savedFuture。运行测试时似乎从未达到ConnectionState.done 案例(在这种情况下我有一个打印件,没有打印出来)。
  • 在一个单独的类(另一个项目)中,我能够使用 FutureBuilder 并对其进行正确测试!
【解决方案2】:

我有一个未来的构建器,它一直在等待,它没有完成或抛出任何错误。 futurebuilder 有一个加载文件的未来,所以它应该能够做到。该应用程序运行良好,但测试却不行。

    // Create the widget by telling the tester to build it.
    await tester.pumpWidget(
        MaterialApp(home: DisplayPictureScreen(key: _globalKey, imagePath: 'test.png', onUpload: upload))
    );
    // Create the Finders.
    final safeAreaFinder = find.byKey(_globalKey);
    // expect we have one positioned widget
    expect(safeAreaFinder, findsOneWidget);
    await tester.pump();
    expect(find.byType(CircularProgressIndicator), findsOneWidget);
    // test code here
    await tester.pump(const Duration(seconds: 20));
    expect(find.byType(Expanded), findsOneWidget);
  });
}```

【讨论】:

    猜你喜欢
    • 2020-07-15
    • 2021-07-07
    • 1970-01-01
    • 1970-01-01
    • 2019-01-18
    • 2021-10-28
    • 2021-08-17
    • 2019-09-07
    • 2019-07-13
    相关资源
    最近更新 更多