[flutter_test] Adds method to mock EventChannels (#123726)
[flutter_test] Adds method to mock EventChannels
This commit is contained in:
parent
4e5e9f4640
commit
f28eb28f0d
@ -265,69 +265,50 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
group('EventChannel', () {
|
group('EventChannel', () {
|
||||||
const MessageCodec<dynamic> jsonMessage = JSONMessageCodec();
|
|
||||||
const MethodCodec jsonMethod = JSONMethodCodec();
|
const MethodCodec jsonMethod = JSONMethodCodec();
|
||||||
const EventChannel channel = EventChannel('ch', jsonMethod);
|
const EventChannel channel = EventChannel('ch', jsonMethod);
|
||||||
void emitEvent(ByteData? event) {
|
|
||||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
|
|
||||||
'ch',
|
|
||||||
event,
|
|
||||||
(ByteData? reply) {},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
test('can receive event stream', () async {
|
test('can receive event stream', () async {
|
||||||
bool canceled = false;
|
bool canceled = false;
|
||||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMessageHandler(
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockStreamHandler(
|
||||||
'ch',
|
channel,
|
||||||
(ByteData? message) async {
|
MockStreamHandler.inline(
|
||||||
final Map<dynamic, dynamic> methodCall = jsonMessage.decodeMessage(message) as Map<dynamic, dynamic>;
|
onListen: (Object? arguments, MockStreamHandlerEventSink events) {
|
||||||
if (methodCall['method'] == 'listen') {
|
events.success('${arguments}1');
|
||||||
final String argument = methodCall['args'] as String;
|
events.success('${arguments}2');
|
||||||
emitEvent(jsonMethod.encodeSuccessEnvelope('${argument}1'));
|
events.endOfStream();
|
||||||
emitEvent(jsonMethod.encodeSuccessEnvelope('${argument}2'));
|
},
|
||||||
emitEvent(null);
|
onCancel: (Object? arguments) {
|
||||||
return jsonMethod.encodeSuccessEnvelope(null);
|
|
||||||
} else if (methodCall['method'] == 'cancel') {
|
|
||||||
canceled = true;
|
canceled = true;
|
||||||
return jsonMethod.encodeSuccessEnvelope(null);
|
},
|
||||||
} else {
|
),
|
||||||
fail('Expected listen or cancel');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
final List<dynamic> events = await channel.receiveBroadcastStream('hello').toList();
|
final List<Object?> events = await channel.receiveBroadcastStream('hello').toList();
|
||||||
expect(events, orderedEquals(<String>['hello1', 'hello2']));
|
expect(events, orderedEquals(<String>['hello1', 'hello2']));
|
||||||
await Future<void>.delayed(Duration.zero);
|
await Future<void>.delayed(Duration.zero);
|
||||||
expect(canceled, isTrue);
|
expect(canceled, isTrue);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can receive error event', () async {
|
test('can receive error event', () async {
|
||||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMessageHandler(
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockStreamHandler(
|
||||||
'ch',
|
channel,
|
||||||
(ByteData? message) async {
|
MockStreamHandler.inline(
|
||||||
final Map<dynamic, dynamic> methodCall = jsonMessage.decodeMessage(message) as Map<dynamic, dynamic>;
|
onListen: (Object? arguments, MockStreamHandlerEventSink events) {
|
||||||
if (methodCall['method'] == 'listen') {
|
events.error(code: '404', message: 'Not Found.', details: arguments);
|
||||||
final String argument = methodCall['args'] as String;
|
},
|
||||||
emitEvent(jsonMethod.encodeErrorEnvelope(code: '404', message: 'Not Found.', details: argument));
|
),
|
||||||
return jsonMethod.encodeSuccessEnvelope(null);
|
|
||||||
} else if (methodCall['method'] == 'cancel') {
|
|
||||||
return jsonMethod.encodeSuccessEnvelope(null);
|
|
||||||
} else {
|
|
||||||
fail('Expected listen or cancel');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
final List<dynamic> events = <dynamic>[];
|
final List<Object?> events = <Object?>[];
|
||||||
final List<dynamic> errors = <dynamic>[];
|
final List<Object?> errors = <Object?>[];
|
||||||
channel.receiveBroadcastStream('hello').listen(events.add, onError: errors.add);
|
channel.receiveBroadcastStream('hello').listen(events.add, onError: errors.add);
|
||||||
await Future<void>.delayed(Duration.zero);
|
await Future<void>.delayed(Duration.zero);
|
||||||
expect(events, isEmpty);
|
expect(events, isEmpty);
|
||||||
expect(errors, hasLength(1));
|
expect(errors, hasLength(1));
|
||||||
expect(errors[0], isA<PlatformException>());
|
expect(errors[0], isA<PlatformException>());
|
||||||
final PlatformException error = errors[0] as PlatformException;
|
final PlatformException? error = errors[0] as PlatformException?;
|
||||||
expect(error.code, '404');
|
expect(error?.code, '404');
|
||||||
expect(error.message, 'Not Found.');
|
expect(error?.message, 'Not Found.');
|
||||||
expect(error.details, 'hello');
|
expect(error?.details, 'hello');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -69,6 +69,7 @@ export 'src/frame_timing_summarizer.dart';
|
|||||||
export 'src/goldens.dart';
|
export 'src/goldens.dart';
|
||||||
export 'src/image.dart';
|
export 'src/image.dart';
|
||||||
export 'src/matchers.dart';
|
export 'src/matchers.dart';
|
||||||
|
export 'src/mock_event_channel.dart';
|
||||||
export 'src/nonconst.dart';
|
export 'src/nonconst.dart';
|
||||||
export 'src/platform.dart';
|
export 'src/platform.dart';
|
||||||
export 'src/restoration.dart';
|
export 'src/restoration.dart';
|
||||||
|
79
packages/flutter_test/lib/src/mock_event_channel.dart
Normal file
79
packages/flutter_test/lib/src/mock_event_channel.dart
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
/// A mock stream handler for an [EventChannel] that mimics the native
|
||||||
|
/// StreamHandler API.
|
||||||
|
///
|
||||||
|
/// The [onListen] callback is provided a [MockStreamHandlerEventSink] with
|
||||||
|
/// the following API:
|
||||||
|
/// - [MockStreamHandlerEventSink.success] sends a success event.
|
||||||
|
/// - [MockStreamHandlerEventSink.error] sends an error event.
|
||||||
|
/// - [MockStreamHandlerEventSink.endOfStream] sends an end of stream event.
|
||||||
|
abstract class MockStreamHandler {
|
||||||
|
/// Create a new [MockStreamHandler].
|
||||||
|
MockStreamHandler();
|
||||||
|
|
||||||
|
/// Create a new inline [MockStreamHandler] with the given [onListen] and
|
||||||
|
/// [onCancel] handlers.
|
||||||
|
factory MockStreamHandler.inline({
|
||||||
|
required MockStreamHandlerOnListenCallback onListen,
|
||||||
|
MockStreamHandlerOnCancelCallback? onCancel,
|
||||||
|
}) => _InlineMockStreamHandler(onListen: onListen, onCancel: onCancel);
|
||||||
|
|
||||||
|
/// Handler for the listen event.
|
||||||
|
void onListen(Object? arguments, MockStreamHandlerEventSink events);
|
||||||
|
|
||||||
|
/// Handler for the cancel event.
|
||||||
|
void onCancel(Object? arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Typedef for the inline onListen callback.
|
||||||
|
typedef MockStreamHandlerOnListenCallback = void Function(Object? arguments, MockStreamHandlerEventSink events);
|
||||||
|
|
||||||
|
/// Typedef for the inline onCancel callback.
|
||||||
|
typedef MockStreamHandlerOnCancelCallback = void Function(Object? arguments);
|
||||||
|
|
||||||
|
class _InlineMockStreamHandler extends MockStreamHandler {
|
||||||
|
_InlineMockStreamHandler({
|
||||||
|
required MockStreamHandlerOnListenCallback onListen,
|
||||||
|
MockStreamHandlerOnCancelCallback? onCancel,
|
||||||
|
}) : _onListenInline = onListen,
|
||||||
|
_onCancelInline = onCancel;
|
||||||
|
|
||||||
|
final MockStreamHandlerOnListenCallback _onListenInline;
|
||||||
|
final MockStreamHandlerOnCancelCallback? _onCancelInline;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onListen(Object? arguments, MockStreamHandlerEventSink events) => _onListenInline(arguments, events);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onCancel(Object? arguments) => _onCancelInline?.call(arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A mock event sink for a [MockStreamHandler] that mimics the native
|
||||||
|
/// [EventSink](https://api.flutter.dev/javadoc/io/flutter/plugin/common/EventChannel.EventSink.html)
|
||||||
|
/// API.
|
||||||
|
class MockStreamHandlerEventSink {
|
||||||
|
/// Create a new [MockStreamHandlerEventSink] with the given [sink].
|
||||||
|
MockStreamHandlerEventSink(EventSink<Object?> sink) : _sink = sink;
|
||||||
|
|
||||||
|
final EventSink<Object?> _sink;
|
||||||
|
|
||||||
|
/// Send a success event.
|
||||||
|
void success(Object? event) => _sink.add(event);
|
||||||
|
|
||||||
|
/// Send an error event.
|
||||||
|
void error({
|
||||||
|
required String code,
|
||||||
|
String? message,
|
||||||
|
Object? details,
|
||||||
|
}) => _sink.addError(PlatformException(code: code, message: message, details: details));
|
||||||
|
|
||||||
|
/// Send an end of stream event.
|
||||||
|
void endOfStream() => _sink.close();
|
||||||
|
}
|
@ -8,6 +8,9 @@ import 'dart:ui' as ui;
|
|||||||
import 'package:fake_async/fake_async.dart';
|
import 'package:fake_async/fake_async.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
import 'mock_event_channel.dart';
|
||||||
|
import 'widget_tester.dart';
|
||||||
|
|
||||||
/// A function which takes the name of the method channel, it's handler,
|
/// A function which takes the name of the method channel, it's handler,
|
||||||
/// platform message and asynchronously returns an encoded response.
|
/// platform message and asynchronously returns an encoded response.
|
||||||
typedef AllMessagesHandler = Future<ByteData?>? Function(
|
typedef AllMessagesHandler = Future<ByteData?>? Function(
|
||||||
@ -197,6 +200,9 @@ class TestDefaultBinaryMessenger extends BinaryMessenger {
|
|||||||
///
|
///
|
||||||
/// * [setMockMethodCallHandler], which wraps this method but decodes
|
/// * [setMockMethodCallHandler], which wraps this method but decodes
|
||||||
/// the messages using a [MethodCodec].
|
/// the messages using a [MethodCodec].
|
||||||
|
///
|
||||||
|
/// * [setMockStreamHandler], which wraps [setMockMethodCallHandler] to
|
||||||
|
/// handle [EventChannel] messages.
|
||||||
void setMockMessageHandler(String channel, MessageHandler? handler, [ Object? identity ]) {
|
void setMockMessageHandler(String channel, MessageHandler? handler, [ Object? identity ]) {
|
||||||
if (handler == null) {
|
if (handler == null) {
|
||||||
_outboundHandlers.remove(channel);
|
_outboundHandlers.remove(channel);
|
||||||
@ -237,6 +243,9 @@ class TestDefaultBinaryMessenger extends BinaryMessenger {
|
|||||||
///
|
///
|
||||||
/// * [setMockMethodCallHandler], which is similar but decodes
|
/// * [setMockMethodCallHandler], which is similar but decodes
|
||||||
/// the messages using a [MethodCodec].
|
/// the messages using a [MethodCodec].
|
||||||
|
///
|
||||||
|
/// * [setMockStreamHandler], which wraps [setMockMethodCallHandler] to
|
||||||
|
/// handle [EventChannel] messages.
|
||||||
void setMockDecodedMessageHandler<T>(BasicMessageChannel<T> channel, Future<T> Function(T? message)? handler) {
|
void setMockDecodedMessageHandler<T>(BasicMessageChannel<T> channel, Future<T> Function(T? message)? handler) {
|
||||||
if (handler == null) {
|
if (handler == null) {
|
||||||
setMockMessageHandler(channel.name, null);
|
setMockMessageHandler(channel.name, null);
|
||||||
@ -302,6 +311,81 @@ class TestDefaultBinaryMessenger extends BinaryMessenger {
|
|||||||
}, handler);
|
}, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set a handler for intercepting stream events sent to the
|
||||||
|
/// platform on the given channel.
|
||||||
|
///
|
||||||
|
/// Intercepted method calls are not forwarded to the platform.
|
||||||
|
///
|
||||||
|
/// The given handler will replace the currently registered
|
||||||
|
/// handler for that channel, if any. To stop intercepting messages
|
||||||
|
/// at all, pass null as the handler.
|
||||||
|
///
|
||||||
|
/// Events are decoded using the codec of the channel.
|
||||||
|
///
|
||||||
|
/// The handler's stream messages are used as a response, after encoding
|
||||||
|
/// them using the channel's codec.
|
||||||
|
///
|
||||||
|
/// To send an error, pass the error information to the handler's event sink.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.flutter_test.TestDefaultBinaryMessenger.handlePlatformMessage.asyncHandlers}
|
||||||
|
///
|
||||||
|
/// Registered handlers are cleared after each test.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [setMockMethodCallHandler], which is the similar method for
|
||||||
|
/// [MethodChannel].
|
||||||
|
///
|
||||||
|
/// * [setMockMessageHandler], which is similar but provides raw
|
||||||
|
/// access to the underlying bytes.
|
||||||
|
///
|
||||||
|
/// * [setMockDecodedMessageHandler], which is similar but decodes
|
||||||
|
/// the messages using a [MessageCodec].
|
||||||
|
void setMockStreamHandler(EventChannel channel, MockStreamHandler? handler) {
|
||||||
|
if (handler == null) {
|
||||||
|
setMockMessageHandler(channel.name, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final StreamController<Object?> controller = StreamController<Object?>();
|
||||||
|
addTearDown(controller.close);
|
||||||
|
|
||||||
|
setMockMethodCallHandler(MethodChannel(channel.name, channel.codec), (MethodCall call) async {
|
||||||
|
switch (call.method) {
|
||||||
|
case 'listen':
|
||||||
|
return handler.onListen(call.arguments, MockStreamHandlerEventSink(controller.sink));
|
||||||
|
case 'cancel':
|
||||||
|
return handler.onCancel(call.arguments);
|
||||||
|
default:
|
||||||
|
throw UnimplementedError('Method ${call.method} not implemented');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final StreamSubscription<Object?> sub = controller.stream.listen(
|
||||||
|
(Object? e) => channel.binaryMessenger.handlePlatformMessage(
|
||||||
|
channel.name,
|
||||||
|
channel.codec.encodeSuccessEnvelope(e),
|
||||||
|
null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
addTearDown(sub.cancel);
|
||||||
|
sub.onError((Object? e) {
|
||||||
|
if (e is! PlatformException) {
|
||||||
|
throw ArgumentError('Stream error must be a PlatformException');
|
||||||
|
}
|
||||||
|
channel.binaryMessenger.handlePlatformMessage(
|
||||||
|
channel.name,
|
||||||
|
channel.codec.encodeErrorEnvelope(
|
||||||
|
code: e.code,
|
||||||
|
message: e.message,
|
||||||
|
details: e.details,
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
sub.onDone(() => channel.binaryMessenger.handlePlatformMessage(channel.name, null, null));
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if the `handler` argument matches the `handler`
|
/// Returns true if the `handler` argument matches the `handler`
|
||||||
/// previously passed to [setMockMessageHandler],
|
/// previously passed to [setMockMessageHandler],
|
||||||
/// [setMockDecodedMessageHandler], or [setMockMethodCallHandler].
|
/// [setMockDecodedMessageHandler], or [setMockMethodCallHandler].
|
||||||
|
@ -69,6 +69,32 @@ void main() {
|
|||||||
expect(result?.buffer.asUint8List(), Uint8List.fromList(<int>[2, 3, 4]));
|
expect(result?.buffer.asUint8List(), Uint8List.fromList(<int>[2, 3, 4]));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Mock StreamHandler is set correctly', () async {
|
||||||
|
const EventChannel channel = EventChannel('');
|
||||||
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockStreamHandler(
|
||||||
|
channel,
|
||||||
|
MockStreamHandler.inline(onListen: (Object? arguments, MockStreamHandlerEventSink events) {
|
||||||
|
events.success(arguments);
|
||||||
|
events.error(code: 'code', message: 'message', details: 'details');
|
||||||
|
events.endOfStream();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
channel.receiveBroadcastStream('argument'),
|
||||||
|
emitsInOrder(<Object?>[
|
||||||
|
'argument',
|
||||||
|
emitsError(
|
||||||
|
isA<PlatformException>()
|
||||||
|
.having((PlatformException e) => e.code, 'code', 'code')
|
||||||
|
.having((PlatformException e) => e.message, 'message', 'message')
|
||||||
|
.having((PlatformException e) => e.details, 'details', 'details'),
|
||||||
|
),
|
||||||
|
emitsDone,
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('Mock AllMessagesHandler is set correctly',
|
testWidgets('Mock AllMessagesHandler is set correctly',
|
||||||
(WidgetTester tester) async {
|
(WidgetTester tester) async {
|
||||||
final TestDefaultBinaryMessenger binaryMessenger =
|
final TestDefaultBinaryMessenger binaryMessenger =
|
||||||
|
Loading…
x
Reference in New Issue
Block a user