Reputation: 2890
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
pump
callspumpAndSettle
tester.runAsync
lineAll 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?
Upvotes: 2
Views: 277
Reputation: 2890
Turns out my overall setup was correct but the Floor
package requires you to add this line to the test whenever you need to await
some async
Floor
operations:
await Future<void>.delayed(const Duration(milliseconds: 100));
The entire code looks like this now:
testWidgets('test add new set', (WidgetTester tester) async {
await pumpApp(
tester, exercisesProvider, setRepository, exerciseRepository,
setProvider);
await tester.runAsync(() async {
var bench = find.text('Bench Press');
expect(bench, findsOneWidget);
await tester.tap(bench);
await tester.pump();
await Future<void>.delayed(const Duration(milliseconds: 100));
await tester.pump();
// Should be found if content is loaded
expect(find.text('Clear'), findsOneWidget);
I found this info in the unit tests of their example project.
Upvotes: 1
Reputation: 546
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
Upvotes: 0