错误"pumpAndSettle超时"可能是由于riverpod



我遇到了一个小部件测试,我可以使用一些帮助来复制行为
请运行下面的代码示例

import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'home_page.dart';
void main() => runApp(
const ProviderScope(
child: MaterialApp(
home: Material(
child: MyHomePage(),
),
),
),
);
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
extension RoundX on double {
double roundToPrecision(int n) {
final f = pow(10, n);
return (this * f).round() / f;
}
}
final tasksPod = Provider<List<Future<void> Function()>>(
(ref) => [
for (var i = 0; i < 10; ++i)
() async {
await Future.delayed(kThemeAnimationDuration);
}
],
);
final progressPod = Provider.autoDispose<ValueNotifier<double>>((ref) {
final notifier = ValueNotifier<double>(0);
ref.onDispose(notifier.dispose);
return notifier;
});
class MyHomePage extends HookWidget {
const MyHomePage() : super(key: const ValueKey('MyHomePage'));
@override
Widget build(BuildContext context) {
final progress = useProvider(progressPod);
final tasks = useProvider(tasksPod);
useMemoized(() async {
final steps = tasks.length;
if (steps < 1) {
progress.value = 1;
} else {
for (final task in tasks) {
final current = progress.value;
if (current >= 1) {
break;
}
await task();
final value = (current + 1 / steps).roundToPrecision(1);
print('$value');
progress.value = value;
}
}
});
return Center(
child: ValueListenableBuilder<double>(
valueListenable: progress,
child: const FlutterLogo(),
builder: (context, value, child) =>
value < 1 ? const CircularProgressIndicator() : child!,
),
);
}
}

运行应用程序一切正常

✓  Built build/app/outputs/flutter-apk/app-debug.apk.
Installing build/app/outputs/flutter-apk/app.apk...                 4.7s
Syncing files to device Pixel 3a...                                 93ms
Flutter run key commands.
r Hot reload. 🔥🔥🔥
R Hot restart.
h Repeat this help message.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).
💪 Running with sound null safety 💪
An Observatory debugger and profiler on Pixel 3a is available at: http://127.0.0.1:36517/50vVndYZ3l4=/
I/flutter (19990): 0.1
I/flutter (19990): 0.2
I/flutter (19990): 0.3
I/flutter (19990): 0.4
I/flutter (19990): 0.5
I/flutter (19990): 0.6
I/flutter (19990): 0.7
The Flutter DevTools debugger and profiler on Pixel 3a is available at: http://127.0.0.1:9101?uri=http%3A%2F%2F127.0.0.1%3A36517%2F50vVndYZ3l4%3D%2F
I/flutter (19990): 0.8
I/flutter (19990): 0.9
I/flutter (19990): 1.0
Application finished.

但未通过测试

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:timeout_issue/home_page.dart';
void main() {
testWidgets(
'WHEN tasks are not completed'
'THEN shows `CircularProgressIndicator`', (tester) async {
TestWidgetsFlutterBinding.ensureInitialized();
await tester.runAsync(() async {
await tester.pumpWidget(
ProviderScope(
child: const MaterialApp(
home: Material(
child: MyHomePage(),
),
),
),
);
await tester.pumpAndSettle(kThemeAnimationDuration);
expect(
find.byType(CircularProgressIndicator),
findsOneWidget,
reason: 'CircularProgressIndicator should be shown',
);
});
});
}

这个输出

00:05 +0: WHEN tasks are not completedTHEN shows `CircularProgressIndicator`                                                                                                                                
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following assertion was thrown while running async test code:
pumpAndSettle timed out
When the exception was thrown, this was the stack:
#0      WidgetTester.pumpAndSettle.<anonymous closure> (package:flutter_test/src/widget_tester.dart:651:11)
<asynchronous suspension>
<asynchronous suspension>
(elided one frame from package:stack_trace)
...
════════════════════════════════════════════════════════════════════════════════════════════════════
00:05 +0 -1: WHEN tasks are not completedTHEN shows `CircularProgressIndicator` [E]                                                                                                                         
Test failed. See exception logs above.
The test description was: WHEN tasks are not completedTHEN shows `CircularProgressIndicator`

00:05 +0 -1: Some tests failed.                

环境为

Flutter version 2.2.0-11.0.pre.176

environment:
sdk: ">=2.12.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
hooks_riverpod: ^0.14.0
flutter_hooks: ^0.16.0

任何帮助都取决于

我认为问题与使用pumpAndSettle和无限动画(循环进度指示器(有关。你可以尝试使用泵,而不需要沉淀来自己构建框架。

https://api.flutter.dev/flutter/flutter_test/WidgetTester/pumpAndSettle.html

看来atm riverpod和pumpAndSettle不起作用,作为一个讨厌的快速破解,你可以尝试这样的方法:

for (int i = 0; i < 5; i++) {
// because pumpAndSettle doesn't work with riverpod
await tester.pump(Duration(seconds: 1));
}

@zuldyc是正确的。通过异步运行该步骤,它为计时器提供了在继续之前成功完成所需的内容。我现在有一个工作的例子,希望能让事情变得更清楚。

破损代码

testWidgets('Testing Login Button Success - New User', (tester) async {
final amplifyAuthMock = MockAmplifyAuth();
final dbInterfaceMock = MockDatabaseInterface();
when(amplifyAuthMock.login('testNew@test.com', 'password!'))
.thenAnswer((result) async => true);
when(dbInterfaceMock.startStopDBSync())
.thenAnswer((realInvocation) async => true);
when(dbInterfaceMock.restartDBSync())
.thenAnswer((realInvocation) async => true);
// CREATING FORM TO TEST
await tester
.pumpWidget(createLoginForm(amplifyAuthMock, dbInterfaceMock));
await inputDummyLoginText(tester, email: 'testNew@test.com');

// PRESSING LOGIN BUTTON AND SHOULD GO TO HOME PAGE
await tester.tap(find.byType(SkillTreeElevatedButton));
// BREAKS HERE ON PUMP AND SETTLE******
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(find.byType(CircularProgressIndicator), findsOneWidget);
});

由于公认答案中所述的原因,它断裂了。嗯,有点。你会得到一种竞争条件,因为我们使用的是异步的future,但上面的代码没有考虑到这一点,所以它执行future小部件的代码,但不知道要等待它完成创建,所以它存在,一切都爆炸了。我们需要使整个过程异步。我们通过遵循Zuldyc的答案来做到这一点。通过将我的代码更改为以下内容,它可以在没有问题的情况下工作

// THE ABOVE CODE HAS NOT CHANGED, NEW CODE STARTS HERE
await tester
.runAsync(() => tester.tap(find.byType(SkillTreeElevatedButton)));
await tester.pump(const Duration(seconds: 1));
expect(find.byType(CircularProgressIndicator), findsOneWidget);
});

需要明确的是,变化如下

//BEFORE
await tester.tap(find.byType(SkillTreeElevatedButton));
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(find.byType(CircularProgressIndicator), findsOneWidget);
//AFTER
await tester.runAsync(() => tester.tap(find.byType(SkillTreeElevatedButton)));
await tester.pump(const Duration(seconds: 1));
expect(find.byType(CircularProgressIndicator), findsOneWidget);

我的点击操作触发了新屏幕和加载指示器,所以我需要使该操作异步,这样它才能完成。

runAsync似乎解决了问题

await tester.runAsync(() => tester.pumpWidget(
ProviderScope(child: MyApp()), const Duration(milliseconds: 100)));
final indicator = const CircularProgressIndicator();
await tester.pumpWidget(indicator);
expect(find.byWidget(indicator), findsOneWidget);

pumpAndSettle()对我不起作用,即使使用runAsync也是如此。我认为原因是来自文档:

如果给定的超时需要更长的时间才能解决,那么测试将失败(此方法将引发异常(。特别是,这意味着如果有无限动画正在进行(例如,如果有不确定的进度指示器在旋转(,则此方法将抛出

我创建了一个更有效的@catalyn答案版本,一旦发现者评估为真,它就会停止跳动。

extension PumpUntilFound on WidgetTester {
Future<void> pumpUntilFound(
Finder finder, {
Duration duration = const Duration(milliseconds: 100),
int tries = 10,
}) async {
for (var i = 0; i < tries; i++) {
await pump(duration);
final result = finder.precache();
if (result) {
finder.evaluate();
break;
}
}
}
}

用法示例:

final finder = find.byType(AuthScreen);
await tester.pumpUntilFound(finder);
expect(finder, findsOneWidget);

对于那些在等待未来时使用CircularProgressIndicator来指示加载状态的人,我发现在我的特定场景中,只需使用

await widgetTester.pump(Duration(seconds: 1));

有助于在1秒延迟后将小部件转换为有效的数据状态。

相关内容

  • 没有找到相关文章

最新更新