如何测试 initState() 中有异步调用的 Flutter 应用程序?

Posted

技术标签:

【中文标题】如何测试 initState() 中有异步调用的 Flutter 应用程序?【英文标题】:How to test Flutter app where there is an async call in initState()? 【发布时间】: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() 中设置持续时间似乎没有任何改变。

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

【问题讨论】:

可能相关? github.com/flutter/flutter/issues/23179 虽然我没有添加资产,但我想创建一个 png 然后从内存中加载它有点像资产? 【参考方案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);
  );
```

【讨论】:

以上是关于如何测试 initState() 中有异步调用的 Flutter 应用程序?的主要内容,如果未能解决你的问题,请参考以下文章

颤振异步调用不会在 initState() 中异步运行

有没有办法在 InitState 方法上加载异步数据?

Flutter:异步任务在“initState()”中无法按预期工作

如何在颤动中从父小部件调用子小部件的initState()方法

如何在initState中调用cubit类中的函数?

如何在 initstate() 中读取和使用共享偏好值?我可以在其他小部件中读取和使用值,但不能在我在 initstate 中调用的 API 中读取和使用值