如何恢复FlutterError.onError?

17

在我的应用中,我将Flutter的错误记录到Crashlytics中。

FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;

当运行集成测试时,如果出现异常,控制台会显示以下语句,并且测试会一直挂起:

运行测试时发生以下异常:I/flutter (30479): 一个测试覆盖了FlutterError.onError但未将其返回到原始状态,或者具有意外的附加错误,无法处理。通常,这是由于在恢复FlutterError.onError之前使用expect()引起的。

控制台中的上述消息提示onError覆盖存在问题,我该如何按照控制台中的建议将FlutterError.onError还原为其原始状态?请注意,我正在使用新推荐的integration test方式。

5个回答

11
onErrorFlutterError的一个公共静态成员,因此从技术上讲,任何人都可以从任何地方覆盖它。函数testWidgets()在运行测试主体之前也会使用自己的错误处理程序覆盖FlutterError.onError。你可以阅读其源代码获取更多信息。

基本上,发生了以下情况:

testWidgets('', (tester) async { // onError is overridden with the handler of the test framework
  await app.main(); // onError is overridden again with crashlytics error handler
  //...
  expect(); // Flutter yells that you should not have touched its onError
});

重点是Flutter测试框架需要其onError正确工作。因此,无论你做什么,请记得调用测试框架的错误处理程序。

下面是我在项目中使用的方式来“恢复”FlutterError.onError(并执行其他操作):

testWidgets('', (tester) async {
  final originalOnError = FlutterError.onError!;
  FlutterError.onError = (FlutterErrorDetails details) {
    // do something like ignoring an exception
    originalOnError(details); // call test framework's error handler
  };
  // ...
  expect();
});

经过一些修改,我认为您的问题是可以解决的。


似乎我们需要在每个expect()调用之前覆盖FlutterError.onError和ErrorWidget.builder。 - Mehul Pamale
@MehulPamale,我认为我们需要在testWidgets()每个测试体的开头重新覆盖onError,因为它很快就会被下一个testWidgets()所覆盖。 - Hieu Pham
如果您的entrypoint.main()有一些代码覆盖了onError(例如FirebaseCrashlytics),旧的onError将被丢弃。我快速解决这个问题的方法是将entrypoint.main()移到originalOnError = FlutterError.onError!之后,但在FlutterError.onError = originalOnError之前。 - Hieu Pham
1
我们的解决方法是 - 创建一个新的test:main_stg.dart文件并将其用于测试,向该文件中的init()传递一个isCalledByIntegrationTest标志(类似的内容),并相应地覆盖FlutterError.onError和ErrorWidget.builder,以便在测试时使用该文件。话虽如此,我们仍需手动比较build和test入口点的内容。 - Mehul Pamale
如何忽略异常? - Jagadeesh
显示剩余2条评论

7

我写的这个辅助函数适合我的需求:


Future<void> restoreFlutterError(Future<void> Function() call) async {
  final originalOnError = FlutterError.onError!;
  await call();
  final overriddenOnError = FlutterError.onError!;

  // restore FlutterError.onError
  FlutterError.onError = (FlutterErrorDetails details) {
    if (overriddenOnError != originalOnError) overriddenOnError(details);
    originalOnError(details);
  };
}

void main(){
  testWidgets("some test", (tester) async {
    await restoreFlutterError(() async {
      app.main();
      await tester.pumpAndSettle();
    });
    // ...
    expect(...);
  });
}

覆盖FlutterError.onError的任何内容都可以包装在restoreFlutterError中——该函数确保调用onError处理程序(您的和测试框架设置的处理程序)。


你能解释一下这段代码吗?originalOnError和overriddenOnError是什么?为什么要进行那个比较? - Jagadeesh
FlutterError.onError 被 "测试框架" 设置(以捕获所有错误),在调用 expect 时需要存在 - 如果您的应用程序/测试代码覆盖它,则会破坏测试。因此,我编写了 restoreFlutterError,它 "缓存" 原始值(由测试框架设置),如果被覆盖,则恢复它 - 它还确保覆盖的 onError 带有错误详细信息。 - Harsh Bhikadia

1
链接-1, 链接-2, 链接-3

简而言之:目前integration_test库似乎只针对“顺畅路径”进行了优化。

在我的情况下,当启用此行时,Firebase与integration_test发生了冲突:

   FirebaseMessaging.onBackgroundMessage(_onBackgroundOrTerminatedHandler);

一旦我禁用了它(在运行集成测试时),错误就消失了。

-1
我看到这个错误,是因为在请求测试点击按钮后,我忘记调用 await tester.pumpAndSettle()
这导致应用程序无法跳转到下一个屏幕。 当我尝试查找特定的TextField时,它对集成测试不可见。
在我的情况下,错误的原因与实际上不需要恢复FlutterError.onError无关。

-2

尝试升级到Flutter 2.5.0,这个问题似乎已经被修复了。

现在运行测试时不再卡住,所以它会继续执行其余的测试。虽然仍然显示_pendingExceptionDetails != null错误。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接