I'm trying to write a test for an app which isn't well-implemented, and has an exception that is thrown inside a Timer
callback. I'd like to ignore that exception inside my integration test, because I'm not interested in that exception.
That's a minimal example of the problem I'm having:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets(
'exception test',
(tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Center(
child: Text('Hello, World!'),
),
),
),
);
// Let's assume this Timer is created inside the app, and I do not
// want to change it.
Timer.periodic(
const Duration(seconds: 2),
(_) => throw Exception('thrown on purpose'),
);
await Future<void>.delayed(Duration(seconds: 3));
);
}
Running the above code with flutter test integration_test/exception_test.dart
gives me:
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following _Exception was thrown running a test:
Exception: thrown on purpose
When the exception was thrown, this was the stack:
#0 main.<anonymous closure>.<anonymous closure> (file:///Users/bartek/example_project/integration_test/exception_test.dart:31:16)
#16 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:189:12)
(elided 15 frames from class _Timer, dart:async, and package:stack_trace)
The test description was:
exception test
════════════════════════════════════════════════════════════════════════════════════════════════════
00:30 +0 -1: exception test [E]
Test failed. See exception logs above.
The test description was: exception test
Now, I'd like to ignore that exception. I tried doing 2 things:
FlutterError.onError
WidgetTester.takeException()
but I have had no success.
I tried resetting the callback (remembering to restore it at the end of the test):
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets(
'exception test',
(tester) async {
final oldCallback = FlutterError.onError;
FlutterError.onError = (details) { /* do nothing on purpose */ };
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Center(
child: Text('Hello, World!'),
),
),
),
);
Timer.periodic(
const Duration(seconds: 2),
(_) => throw Exception('thrown on purpose'),
);
await Future<void>.delayed(Duration(seconds: 3));
FlutterError.onError = oldCallback;
},
);
}
but the internal assertion is still violated:
'package:flutter_test/src/binding.dart': Failed assertion: line 954 pos 14: '_pendingExceptionDetails != null': 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.
dart:core _AssertionError._throwNew
package:flutter_test/src/binding.dart 954:14 TestWidgetsFlutterBinding._runTest.handleUncaughtError
package:flutter_test/src/binding.dart 959:9 TestWidgetsFlutterBinding._runTest.<fn>
I also tried doing:
FlutterError.onError = (details) {
tester.takeException();
oldCallback!(details);
};
but this, understandably, gives the original error.
Thanks to @ermekk, who pointed me to this post, I was able to implement this:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets(
'exception test',
(tester) async {
await runZonedGuarded(
() async {
final oldCallback = FlutterError.onError;
FlutterError.onError = (details) {/* do nothing on purpose */};
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Center(
child: Text('Hello, World!'),
),
),
),
);
Timer.periodic(
const Duration(seconds: 2),
(_) => throw Exception('thrown on purpose'),
);
await Future<void>.delayed(Duration(seconds: 3));
FlutterError.onError = oldCallback;
},
(error, stack) {
print('zone caught error: $error');
},
);
},
);
}
It's the good-enough temporary solution I was looking for. Of course, it suppresses all errors, so be careful and remove it as soon as the original exception is fixed and no longer thrown.
Also, for a reason which I didn't investigate, the test never finishes when there's a failing assertion, e.g. expect(true, false)
.