-
Notifications
You must be signed in to change notification settings - Fork 28.9k
Description
This is a revival of, and copy-paste from, #92254. We need the feature mentioned in there in our Patrol testing framework.
Use case
I'd like to be in control of the test binding I choose for integration tests. In my case, I have a custom binding to set the TestBindingEventSource of all touch events as test
, not device
.
That's how my custom binding looks like.
The cookbook contains documentation about adding the ensureInitialized call, but (at least on the latest beta version) flutter run and flutter test generate an intermediate file that calls:
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
That intermediate file is called listener.dart
autogenerated in a path like /var/folders/z8/vl6s_16515s6h_j8z7bs1tmm0000gn/T/flutter_tools.LfIUH0/flutter_test_listener.SK8Sr1/listener.dart
. To find its location, run flutter test integration_test -v
(without -v
the path to the autogenerated file is not shown).
Here's its code:
listener.dart
Here's the method which generates this file.
// @dart=2.17
import 'dart:async';
import 'dart:convert'; // flutter_ignore: dart_convert_import
import 'dart:io'; // flutter_ignore: dart_io_import
import 'dart:isolate';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'dart:developer' as developer;
import 'package:test_api/src/remote_listener.dart';
import 'package:stream_channel/stream_channel.dart';
import 'package:stack_trace/stack_trace.dart';
import 'file:///Users/bartek/dev/leancode/patrol/packages/patrol/example/integration_test/example_test.dart' as test;
/// Returns a serialized test suite.
StreamChannel<dynamic> serializeSuite(Function getMain()) {
return RemoteListener.start(getMain);
}
Future<void> _testMain() async {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
await Future(test.main);
}
/// Capture any top-level errors (mostly lazy syntax errors, since other are
/// caught below) and report them to the parent isolate.
void catchIsolateErrors() {
final ReceivePort errorPort = ReceivePort();
// Treat errors non-fatal because otherwise they'll be double-printed.
Isolate.current.setErrorsFatal(false);
Isolate.current.addErrorListener(errorPort.sendPort);
errorPort.listen((dynamic message) {
// Masquerade as an IsolateSpawnException because that's what this would
// be if the error had been detected statically.
final IsolateSpawnException error = IsolateSpawnException(
message[0] as String);
final Trace stackTrace = message[1] == null ?
Trace(const <Frame>[]) : Trace.parse(message[1] as String);
Zone.current.handleUncaughtError(error, stackTrace);
});
}
void main() {
String serverPort = Platform.environment['SERVER_PORT'] ?? '';
String server = Uri.decodeComponent('ws%3A%2F%2F127.0.0.1:$serverPort');
StreamChannel<dynamic> testChannel = serializeSuite(() {
catchIsolateErrors();
goldenFileComparator = LocalFileComparator(Uri.parse('file:///Users/bartek/dev/leancode/patrol/packages/patrol/example/integration_test/example_test.dart'));
autoUpdateGoldenFiles = false;
return _testMain;
});
final callback = (method, params) async {
testChannel.sink.add(json.decode(params['data'] as String));
// Result is ignored but null is not accepted here.
return developer.ServiceExtensionResponse.result('{}');
};
developer.registerExtension('ext.flutter.integrationTest', callback);
testChannel.stream.listen((x) {
developer.postEvent(
'Flutter.IntegrationTest',
{'data': json.encode(x)},
);
});
}
Proposal
Remove ensureInitialized from the generated file so users can choose what binding (and what configuration) they want the test to use (or at least allow to initialize only subclasses of IntegrationTestWidgetsFlutterBinding
).
Also I think that generating this file is not communicated clearly enough (or at all) by the integration testing docs, which are focused only on very simple cases.