I am writing tests for a Widget that makes use of a Floor
database to store and retrieve data. The test cases use a inMemory database instead of a mocked one. This means all the data operations are genuinely async
in the tests as well. The problem I am facing is that my async
method calls are being completed well after the tests completed.
I have the following unit test:
testWidgets('test add new set', (WidgetTester tester) async {
await pumpApp(tester, exercisesProvider, setRepository, exerciseRepository);
await tester.runAsync(() async {
var bench = find.text('Bench Press');
expect(bench, findsOneWidget);
await tester.tap(bench);
await tester.pump();
await tester.pump();
// Should be found if content is loaded
expect(find.text('Clear'), findsOneWidget);
});
The test fails with the following output:
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following TestFailure was thrown while running async test code:
Expected: exactly one matching node in the widget tree
Actual: _TextFinder:<zero widgets with text "Clear" (ignoring offstage widgets)>
Which: means none were found but one was expected
...
The exception was caught asynchronously.
════════════════════════════════════════════════════════════════════════════════════════════════════
FETCH COMPLETED
The widget under test:
@override
Widget build(BuildContext context) {
// This async method is executed too late
Provider.of<SetProvider>(context, listen: false)
.fetchGroupedSetsByExercise(widget.selectedExercise.id!);
return Column(
children: [
Expanded(
child: Consumer<SetProvider>(builder: (context, setProvider, chil
final groupedSets = setProvider.groupedSets;
if (setProvider.groupedSetsState == IOState.loading ||
setProvider.groupedSetsState == IOState.initial) {
return ...;
} else if (setProvider.groupedSetsState == IOState.error) {
return ...;
} else if (groupedSets.isEmpty) {
return ...;
}
// Contains the clear button and more
return buildSetList(groupedSets, setProvider);
}),
),
],
);
The provider:
Future<void> fetchGroupedSetsByExercise(int exerciseID,
{bool? reverse}) async {
groupedSets = groupSetsByDate(
await fetchSetsByExercise(exerciseID, reverse: reverse));
lastSetEntry = groupedSets.values.lastOrNull?.lastOrNull;
groupedSetsState = IOState.success;
notifyListeners();
print("FETCH COMPLETED");
}
As you can see in the test output fetchGroupedSetsByExercise
is always executed after the test is already over. I am aware that you need to use await
to wait for a async
function to complete however, how do I test my widget? My widget keeps track of the fetch state itself (in groupedSetsState
) and I cannot await
anything, rather the provider will notify the widget once the data arrived.
I have also tried using:
FutureBuilder
- Awaiting multiple
pump
calls - Awaiting
pumpAndSettle
- Removing the
tester.runAsync
line
All with the same result, the test fails because the async
method is being executed too late. How do I write genuine async tests like in my case?
2
Answers
Turns out my overall setup was correct but the
Floor
package requires you to add this line to the test whenever you need toawait
someasync
Floor
operations:The entire code looks like this now:
I found this info in the unit tests of their example project.
True async testing can be achieved via mockito. You can create stubs for your DB call. You’ll be able to intercept when that function completes using
unitlCalled()