17

In my app, I record the flutter onError to crashalytics,

FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;

While running the integration test, if some exceptions happens I get the below statement in the console and the test just hangs,

The following exception was thrown running a test: I/flutter (30479): A test overrode FlutterError.onError but either failed to return it to its original state, or had unexpected additional errors that it could not handle. Typically, this is caused by using expect() before restoring FlutterError.onError.

The above message in console suggests something is wrong with the onError overriding, how do I return FlutterError.onError to its original state as per the recommendation coming up in console.

Please note that I am using the newly recommended way for integration test,

MeanMan
  • 1,040
  • 10
  • 25

5 Answers5

11

onError is a public static member of FlutterError, so technically it can be overridden by anyone from anywhere. The testWidgets() function itself overrides the FlutterError.onError with its own error handler as well before running the test body. You can read its source code for more info.

Basically, below is what happened:

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
});

The point is the Flutter test framework needs its onError to work properly. So whatever you do, remember to call the error handler of the test framework.

Below is the way I used in my project to "restore" the FlutterError.onError (and do something else):

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();
});

With some modification, I think your problem is solvable.

Hieu Pham
  • 216
  • 3
  • 7
  • `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 }; entrypoint.main(); expect(); }); ` @Hieu Pham do you mean this? could you please give some real code without any assumptions ? – Mehul Pamale Apr 13 '22 at 14:14
  • seems like we need to override FlutterError.onError & ErrorWidget.builder before every expect() call – Mehul Pamale Apr 14 '22 at 05:59
  • @MehulPamale I think we need to re-override `onError` at the beginning of every test body of `testWidgets()` since it will be soon overridden by the next `testWidgets()`. – Hieu Pham Apr 14 '22 at 16:24
  • @MehulPamale If your `entrypoint.main()` has some code that overrrides the `onError` (e.g. FirebaseCrashlytics), the old `onError` will be discarded. My quick fix for that is to move up the `entrypoint.main()` right after the `originalOnError = FlutterError.onError!`, but before `FlutterError.onError = originalOnError`. – Hieu Pham Apr 14 '22 at 16:39
  • 1
    Our workaround was - make a new test:main_stg.dart & use this file for testing, pass a flag isCalledByIntegrationTest to the init() in this file (something similar) & accordingly override FlutterError.onError & ErrorWidget.builder & use this file for testing. That being said, we will have to manually keep comparing contents of build & test entrypoint – Mehul Pamale Apr 17 '22 at 14:41
  • How to ignore the exception? – Jagadeesh Dec 20 '22 at 05:32
  • @Jagadeesh you can get the type of the current exception via `details.exception.runtimeType`, then something like `if (currentType == ignoredType) return` – Hieu Pham Dec 20 '22 at 11:17
7

This helper function I wrote works for me:


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(...);
  });
}

Anything that overrides FlutterError.onError can be wrapped around restoreFlutterError - the function makes sure that both onError handler (your and the one set by the test framework) are called.

Harsh Bhikadia
  • 10,095
  • 9
  • 49
  • 70
  • Can you explain the code please? what is originalOnError & overriddenOnError? and why that comparison you were doing? – Jagadeesh Dec 15 '22 at 06:28
  • `FlutterError.onError` is being set by the "Test Framework" (to catch all errors) and requires to be their when `expect` is called - if your app/test code overrides it then it breaks the tests. Therefore I wrote `restoreFlutterError` which "caches" the original (set by test framework) and if it is overriden then restores it - it also makes sure that the overriden `onError` is called with error-details. – Harsh Bhikadia Dec 20 '22 at 16:43
1

hmm, Ok.. it seems that we have a couple a issues related to that : link-1, link-2, link-3

Long story short: The integration_test lib seems to be optimized only for "happy paths" for now.

In my case, Firebase was conflicting with integration_test when this line was enabled:

   FirebaseMessaging.onBackgroundMessage(_onBackgroundOrTerminatedHandler);

The error was gone once I've disabled it (when running the integration tests).

Gui Silva
  • 1,341
  • 15
  • 18
-1

I was seeing this error because I forgot to call await tester.pumpAndSettle() after asking the test to tap a button.

This prevented the app from moving to the next screen. When I tried to find a specific TextField, it wasn't visible to the integration test.

The cause of the error in my case had nothing to do with actually needing to restor FlutterError.onError.

Code on the Rocks
  • 11,488
  • 3
  • 53
  • 61
-2

Try to update to flutter 2.5.0. This seems to be fixed there.

It no longer gets stuck when running the test. So it continues to execute the rest of the tests. Although it still shows the _pendingExceptionDetails != null error

Bonco
  • 122
  • 2
  • 5