错误“pumpAndSettle 超时”可能是由于 riverpod
error `pumpAndSettle timed out` MAYBE due to 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));
}
看来 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);
@zuldyc 是正确的。通过 运行 异步步骤,它为 Timer 提供了在继续之前成功完成所需的内容。我现在有了一个工作示例,希望它能让事情变得更清楚。
损坏的代码
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);
});
由于接受的答案中描述的原因,它中断了。好吧,有点。你会得到一种竞争条件,因为我们使用的是异步的未来,但上面的代码没有考虑到这一点,所以它执行未来小部件的代码但不知道等待它完成创建,所以它存在并且一切都爆炸了。我们需要使整个过程异步。我们通过遵循 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);
我的点击操作触发了新屏幕和加载指示器,所以我需要使该操作异步以便完成。
我无法进行小部件测试,需要一些帮助
要重现该行为,请 运行 下面的 代码示例
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));
}
看来 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);
@zuldyc 是正确的。通过 运行 异步步骤,它为 Timer 提供了在继续之前成功完成所需的内容。我现在有了一个工作示例,希望它能让事情变得更清楚。
损坏的代码
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);
});
由于接受的答案中描述的原因,它中断了。好吧,有点。你会得到一种竞争条件,因为我们使用的是异步的未来,但上面的代码没有考虑到这一点,所以它执行未来小部件的代码但不知道等待它完成创建,所以它存在并且一切都爆炸了。我们需要使整个过程异步。我们通过遵循 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);
我的点击操作触发了新屏幕和加载指示器,所以我需要使该操作异步以便完成。