我在对在Future
期间抛出异常的小部件进行小部件测试时遇到问题。
重现该问题的代码
这是一个简单的testwidget,它以非常简单的方式重现了问题(感谢Remi Rousselet简化了问题)。
testWidgets('This test should pass but fails', (tester) async {
final future = Future<void>.error(42);
await tester.pumpWidget(FutureBuilder(
future: future,
builder: (_, snapshot) {
return Container();
},
));
});
预期结果
我希望测试能够顺利完成。相反,它失败并显示以下错误:
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The number 42 was thrown running a test.
When the exception was thrown, this was the stack:
#2 main.<anonymous closure> (file:///C:/Projects/projet_65/mobile_app/test/ui/exception_test.dart:79:18)
#5 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:0:0)
#8 TestWidgetsFlutterBinding._runTest (package:flutter_test/src/binding.dart:577:14)
#9 AutomatedTestWidgetsFlutterBinding.runTest.<anonymous closure> (package:flutter_test/src/binding.dart:993:24)
#15 AutomatedTestWidgetsFlutterBinding.runTest (package:flutter_test/src/binding.dart:990:15)
#16 testWidgets.<anonymous closure> (package:flutter_test/src/widget_tester.dart:106:22)
#17 Declarer.test.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/declarer.dart:168:27)
#20 Declarer.test.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/declarer.dart:0:0)
#21 Invoker.waitForOutstandingCallbacks.<anonymous closure> (package:test_api/src/backend/invoker.dart:250:15)
#27 Invoker._onRun.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/invoker.dart:399:21)
(elided 17 frames from class _FakeAsync, package dart:async, and package dart:async-patch)
The test description was:
This test should pass but fails
════════════════════════════════════════════════════════════════════════════════════════════════════
我尝试过的事情
我尝试expect
错误,就像如果不在Future
中那样:
expect(tester.takeException(), equals(42));
但是该语句失败,并出现以下错误
The following TestFailure object was thrown running a test (but after the test had completed):
Expected: 42
Actual: null
编辑:
@shb答案对于暴露的情况是正确的。这是一个略微的修改,可以打破它并退回初始错误。这种情况更容易在实际应用中发生(我就是这种情况)
testWidgets('This test should pass but fails', (tester) async {
Future future;
await tester.pumpWidget(
MaterialApp(
home: Row(
children: <Widget>[
FlatButton(
child: const Text('GO'),
onPressed: () { future = Future.error(42);},
),
FutureBuilder(
future: future,
builder: (_, snapshot) {
return Container();
},
),
],
),
));
await tester.tap(find.text('GO'));
});
注意:我自愿忽略了@shb提出的tester.runAsync
来匹配最初的问题,因为它在特定情况下不起作用
答案 0 :(得分:7)
用await tester.runAsync(() async { .. }
从official documentation runAsync<T>
运行执行真正的异步工作的回调。
这是针对需要调用异步方法的调用者的 方法产生隔离或操作系统线程的位置,因此不能 通过调用Pump同步执行。
见下文
testWidgets('This test should pass but fails', (tester) async {
await tester.runAsync(() async {
final future = Future<void>.error(42);
await tester.pumpWidget(FutureBuilder(
future: future,
builder: (_, snapshot) {
return Container();
},
));
});
});
编辑:
(提出第二期OP)
在这种情况下,请使用
Future.delayed(Duration.zero, () {
tester.tap(find.text('GO'));
});
下面的完整代码段
testWidgets('2nd try This test should pass but fails', (tester) async {
Future future;
await tester.runAsync(() async {
await tester.pumpWidget(
MaterialApp(
home: Row(
children: <Widget>[
FlatButton(
child: const Text('GO'),
onPressed: () {
future = Future.error(42);
},
),
FutureBuilder(
future: future,
builder: (_, snapshot) {
return Container();
},
),
],
),
),
);
Future.delayed(Duration.zero, () {tester.tap(find.text('GO'));});
});
});
编辑2:
后来发现
Future.delayed(Duration.zero, () { tester.tap(find.text('GO')); });
没有被呼叫。
答案 1 :(得分:6)
未来会向听众报告错误。如果Future
没有侦听器,它将通知Zone
未捕获的错误(src)。这是测试框架从中获取错误的地方。
克服此错误的一种方法是在使Future
错误之前等待侦听器。
testWidgets('This passes', (tester) async {
final Completer completer = Completer();
await tester.pumpWidget(FutureBuilder(
future: completer.future,
builder: (_, snapshot) {
return Container();
},
));
// has subscribers, doesn't inform Zone about uncought error
completer.completeError(42);
tester.pumpAndSettle();
});
答案 2 :(得分:3)
我认为问题是您没有抓住错误,这会使应用程序崩溃。
我尝试捕获错误,测试通过:
代码如下:
testWidgets('This test should pass but fails', (tester) async {
Future future;
await tester.pumpWidget(MaterialApp(
home: Row(
children: <Widget>[
FlatButton(
child: const Text('GO'),
onPressed: () {
future = Future.error(42).catchError((error) {});
},
),
FutureBuilder(
future: future,
builder: (_, snapshot) {
return Container();
},
),
],
),
));
await tester.tap(find.text('GO'));
});