How to test Flutter app where there is an async call in initState()?
Consider using a FutureBuilder
if your build
method relies on async work.
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
},
)
You can also update your test code like this:
expect(find.byType(OverflowBox), findsOneWidget);
Mary
Updated on November 26, 2022Comments
-
Mary over 1 year
I have a StatefulWidget that does an async call in its
initState()
, to build a Widget. When I manually run this, the widget does build quickly.However, in my test, even if I use
await tester.pump()
orawait tester.pumpAndSettle()
, the widget doesn't seem to get built, until way after the test has run.Widget code:
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); }); }
Test code:
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); }); }
An output when I run my test results in:
failed: Error caught by Flutter test framework, thrown running a test. Expected: <1> Actual: <0>
But if I set
_coolWidget = OverflowBox();
ininitState()
, the test passes.I have other tests that run after this one. After those ones are done, I see the print logging the
print(img);
andprint(_coolWidget);
from above, and it correctly logs the drawn image.I also get the
no longer mounted
print, but that only happens as the very last print, prior to Flutter's built in(tearDownAll)
.Setting durations in the pump() and pumpAndSettle() don't seem to change anything.
I'm probably missing something obvious.
-
Mary about 5 yearsI've put the
FutureBuilder<Image>(...)
code into my initState(), since I want the widget to be built as early as possible. Visually it behaves as expected, though when I run a test it still doesn't seem to find my widget. -
Dan Field about 5 yearsI don't think you want the
FutureBuilder
in your initstate. You could try to make yourbuildWidgetFromCanvas
cache its last value and call it from initState. -
Mary about 5 yearsNow, in initState() I have set a var,
_savedFuture = _buildWidgetFromCanvas()
. I've moved myFutureBuilder
into build(), and myfuture
property is now_savedFuture
. It seems that theConnectionState.done
case is never reached when running the test (I have a print in that case, which is not printed). -
Mary about 5 yearsIn a separate class (another project) I was able to use FutureBuilder and test it properly!