如何在颤振小部件测试中捕获来自未来的错误?
Posted
技术标签:
【中文标题】如何在颤振小部件测试中捕获来自未来的错误?【英文标题】:How to catch an error coming from a Future in flutter widget test? 【发布时间】:2019-10-29 05:11:27 【问题描述】:我在对在 Future
期间引发异常的小部件进行小部件测试时遇到问题。
重现问题的代码
这是一个简单的测试小部件,它以非常简单的方式重现问题(感谢 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
以匹配最初的问题,因为它在特定情况下不起作用
【问题讨论】:
(没关系,忽略我之前的cmets。) @Muldec 如果将tester.tap
包裹在runAsync
中会发生什么?
@RémiRousselet 相同的错误加上一个新的The following assertion was thrown running a test (but after the test had completed): 'package:flutter_test/src/binding.dart': Failed assertion: line 1020 pos 12: '_currentFakeAsync !=null': is not true.
(意味着测试引发了两个错误)
【参考方案1】:
用await tester.runAsync(() async ..
包装你的代码
来自official documentationrunAsync<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'));
);
下面是完整的sn-p
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')); );
没有被调用。
【讨论】:
感谢您的回答。这解决了最初提出的问题。我编辑了帖子以记入您的答案并添加了此解决方案不起作用的代码(问题最初已被过度简化)。你怎么看? 测试没有失败,但Future.delayed
中的Future
也没有被调用。我添加了print
语句来检查代码是否运行,但控制台上没有显示任何内容。
@Muldec 是的,看起来如此。我已经相应地编辑了答案
你试过等待未来吗?也可以考虑Future.microtask
,而不是Future.delayed
。
@RémiRousselet Future.microtask 确实执行但它也不起作用【参考方案2】:
期货向其听众报告错误。如果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();
);
【讨论】:
如果您可以控制错误,这是正确的。根据特定情况抛出错误的代码怎么样?你会怎么处理?我用稍微复杂的代码更新了我的问题。【参考方案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'));
);
【讨论】:
是和不是。当future builder中出现错误时,它将数据作为null。但是对于这种情况,Future 本身应该使用 catchError。 @SergioBernal 的观点似乎有效。 Future.value(42) 的测试通过; 问题是:Future
上的catchError
处理错误并破坏了FutureBuilder.builder
中snapshot.hasError
的点。如果错误已被处理,这将返回 false。因此,测试(和使用)FutureBuilder
的行为变得不清楚为什么向用户显示的错误消息是在 !snapshot.hasData
而不是在 snapshot.hasError
上完成的。
@SergioBernal FutureBuilder 已经在Future
上调用了catchError
。问题不在于它没有被捕获,而是被异步捕获。以上是关于如何在颤振小部件测试中捕获来自未来的错误?的主要内容,如果未能解决你的问题,请参考以下文章