如何测试 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<Image>(...)
代码放入我的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 应用程序?的主要内容,如果未能解决你的问题,请参考以下文章
Flutter:异步任务在“initState()”中无法按预期工作
如何在颤动中从父小部件调用子小部件的initState()方法
如何在 initstate() 中读取和使用共享偏好值?我可以在其他小部件中读取和使用值,但不能在我在 initstate 中调用的 API 中读取和使用值