基于Floor数据库的Widget真实异步测试方案咨询
问题:测试真实异步数据库操作时,Provider异步方法执行滞后导致测试失败
在使用Floor内存数据库测试Widget时,所有数据操作为真实异步操作,但测试中fetchGroupedSetsByExercise总是在测试断言完成后才执行完毕,导致断言找不到"Clear"按钮失败。测试输出显示FETCH COMPLETED在测试异常之后才打印。
测试代码
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); }); });
被测Widget关键代码
@override Widget build(BuildContext context) { // 此异步方法执行时机滞后 Provider.of<SetProvider>(context, listen: false) .fetchGroupedSetsByExercise(widget.selectedExercise.id!); return Column( children: [ Expanded( child: Consumer<SetProvider>(builder: (context, setProvider, child) { final groupedSets = setProvider.groupedSets; if (setProvider.groupedSetsState == IOState.loading || setProvider.groupedSetsState == IOState.initial) { return ...; // 加载中UI } else if (setProvider.groupedSetsState == IOState.error) { return ...; // 错误UI } else if (groupedSets.isEmpty) { return ...; // 空数据UI } // 包含Clear按钮的列表 return buildSetList(groupedSets, setProvider); }), ), ], ); }
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"); }
解决方案
1. 手动等待Provider的异步任务完成
在测试中直接获取Provider实例,手动调用并等待异步方法完成后再执行断言:
testWidgets('test add new set', (WidgetTester tester) async { await pumpApp(tester, exercisesProvider, setRepository, exerciseRepository); var bench = find.text('Bench Press'); expect(bench, findsOneWidget); await tester.tap(bench); await tester.pump(); // 获取当前Widget对应的SetProvider实例 final setProvider = Provider.of<SetProvider>( tester.element(find.byType(YourTargetWidget)), listen: false ); // 等待异步加载完成 await setProvider.fetchGroupedSetsByExercise(widget.selectedExercise.id!); // 触发Widget重建 await tester.pump(); expect(find.text('Clear'), findsOneWidget); });
2. 结合pumpAndSettle与状态监听
利用pumpAndSettle处理所有待处理帧,同时循环等待Provider状态变为成功:
testWidgets('test add new set', (WidgetTester tester) async { await pumpApp(tester, exercisesProvider, setRepository, exerciseRepository); var bench = find.text('Bench Press'); expect(bench, findsOneWidget); await tester.tap(bench); await tester.pump(); final setProvider = Provider.of<SetProvider>( tester.element(find.byType(YourTargetWidget)), listen: false ); // 循环等待状态变为success,同时处理帧 while (setProvider.groupedSetsState != IOState.success) { await tester.pump(const Duration(milliseconds: 100)); } // 或者直接用pumpAndSettle等待所有异步操作完成 // await tester.pumpAndSettle(const Duration(seconds: 1)); expect(find.text('Clear'), findsOneWidget); });
3. 优化Widget中异步方法的触发时机(可选)
避免在build方法中直接调用异步方法(build可能被频繁触发),改用initState结合addPostFrameCallback确保只执行一次:
class YourTargetWidget extends StatefulWidget { // ... 原Widget参数 } class _YourTargetWidgetState extends State<YourTargetWidget> { @override void initState() { super.initState(); // 在第一帧绘制完成后触发异步加载 WidgetsBinding.instance.addPostFrameCallback((_) { Provider.of<SetProvider>(context, listen: false) .fetchGroupedSetsByExercise(widget.selectedExercise.id!); }); } @override Widget build(BuildContext context) { return Column( // ... 原Widget内容 ); } }
内容的提问来源于stack exchange,提问作者Josip Domazet
相关产品推荐
相关产品推荐

