Home > Blockchain >  Why does an unawaited Future error make my test fail?
Why does an unawaited Future error make my test fail?

Time:11-27

Given this minimal test

      test('ignore failures', () {
        Future<void> failingFunc() async {
          throw Exception('BOOOOM');
        }

        failingFunc();
      });

The test fails with this output :

Exception: BOOOOM
main.<fn>.<fn>.<fn>.failingFunc
test/…/auth/auth_action_bloc_test.dart:374
main.<fn>.<fn>.<fn>
test/…/auth/auth_action_bloc_test.dart:377
===== asynchronous gap ===========================
dart:async                                                 _AsyncAwaitCompleter.completeError
test/unit/features/auth/auth_action_bloc_test.dart         main.<fn>.<fn>.<fn>.failingFunc
test/…/auth/auth_action_bloc_test.dart:1
main.<fn>.<fn>.<fn>
test/…/auth/auth_action_bloc_test.dart:377

In don't understand why that test fails.

My understanding is that failingFunc() would fail “silently” because it’s not awaited. (i.e. the exception would be thrown “somewhere else”, and just ignored) I’m (almost) certain that such a thing in production code would work just fine.

The following test does fail the way I expect it to (i.e. adding an await ) :

      test('ignore failures', () async {
        Future<void> failingFunc() async {
          throw Exception('BOOOOM');
        }

        await failingFunc();
      });

and it fails as expected with

Exception: BOOOOM
main.<fn>.<fn>.<fn>.failingFunc
test/…/auth/auth_action_bloc_test.dart:374
main.<fn>.<fn>.<fn>
test/…/auth/auth_action_bloc_test.dart:377

CodePudding user response:

The exception will be thrown somewhere else, but it will most certainly not be ignored.

An uncaught asynchronous error, which this is, is reported to the current Zone's uncaught error handler. If you do nothing special, that's the root zone, which will terminate the program on an uncaught error.

Well, it will do so in native code. On the web, you can't crash the browser, so it will just be treated as an unhandled JavaScript error, which may or may not be ignored. It's still bad style, and makes debugging harder, so you shouldn't let errors be unhandled in production.

The test package runs each test in a new zone with an uncaught-error handler, so it knows which test caused the exception. It also recognizes that the error happened after it had already considered that test done and successful. If you're lucky, it will go back and mark the test as failing from throwing that error, even if it does so much later. If unlucky, and slow, and all tests are done and the test runner has printed the results, then it might be too late to go back and change the result.

Example:

import "package:test/test.dart";

void main() {
  test("example", () {
    Future.delayed(Duration(seconds: 1), () => throw "error");
  });
  test("delayed", () => Future.delayed(Duration(seconds: 2)));
}

In this example, the test package recognizes that the "example" test fails, even if it does so after it has returned and pretended all is well. If you change the duration of "delayed" to zero, then it reports that both tests are successful, before the first tests uncaught error happens, and it just prints 00:00 2: All tests passed!.

If you actually want to ignore errors from a future, you have to do something. Since Dart 2.14, you can write:

 failingFunc().ignore();

The ignore() is an extension method on Future declared in dart:async/dart:core which catches and ignores errors from the future it's called on. Use with care, errors might be significant.

  • Related