Unverified Commit 02b10801 authored by Dan Field's avatar Dan Field Committed by GitHub

Functionality to check handlers set on platform channels (#57696)

parent cdef6779
......@@ -41,20 +41,35 @@ abstract class BinaryMessenger {
/// argument.
///
/// The handler's return value, if non-null, is sent as a response, unencoded.
void setMessageHandler(String channel, Future<ByteData> handler(ByteData message));
void setMessageHandler(String channel, MessageHandler handler);
/// Returns true if the `handler` argument matches the `handler` previously
/// passed to [setMessageHandler].
///
/// This method is useful for tests or test harnesses that want to assert the
/// handler for the vspecified channel has not been altered by a previous test.
bool checkMessageHandler(String channel, MessageHandler handler);
/// Set a mock callback for intercepting messages from the [send] method on
/// this class, on the given channel, without decoding them.
///
/// The given callback will replace the currently registered mock callback for
/// that channel, if any. To remove the mock handler, pass null as the
/// [handler] argument.
/// `handler` argument.
///
/// The handler's return value, if non-null, is used as a response, unencoded.
///
/// This is intended for testing. Messages intercepted in this manner are not
/// sent to platform plugins.
void setMockMessageHandler(String channel, Future<ByteData> handler(ByteData message));
void setMockMessageHandler(String channel, MessageHandler handler);
/// Returns true if the `handler` argument matches the `handler` previously
/// passed to [setMockMessageHandler].
///
/// This method is useful for tests or test harnesses that want to assert the
/// mock handler for the specified channel has not been altered by a previous
/// test.
bool checkMockMessageHandler(String channel, MessageHandler handler);
}
/// The default instance of [BinaryMessenger].
......
......@@ -293,6 +293,9 @@ class _DefaultBinaryMessenger extends BinaryMessenger {
});
}
@override
bool checkMessageHandler(String channel, MessageHandler handler) => _handlers[channel] == handler;
@override
void setMockMessageHandler(String channel, MessageHandler handler) {
if (handler == null)
......@@ -300,4 +303,7 @@ class _DefaultBinaryMessenger extends BinaryMessenger {
else
_mockHandlers[channel] = handler;
}
@override
bool checkMockMessageHandler(String channel, MessageHandler handler) => _mockHandlers[channel] == handler;
}
......@@ -98,6 +98,9 @@ class BasicMessageChannel<T> {
}
}
Expando<Object> _methodChannelHandlers = Expando<Object>();
Expando<Object> _methodChannelMockHandlers = Expando<Object>();
/// A named channel for communicating with platform plugins using asynchronous
/// method calls.
///
......@@ -372,12 +375,22 @@ class MethodChannel {
/// similarly to what happens if no method call handler has been set.
/// Any other exception results in an error envelope being sent.
void setMethodCallHandler(Future<dynamic> handler(MethodCall call)) {
_methodChannelHandlers[this] = handler;
binaryMessenger.setMessageHandler(
name,
handler == null ? null : (ByteData message) => _handleAsMethodCall(message, handler),
handler == null
? null
: (ByteData message) => _handleAsMethodCall(message, handler),
);
}
/// Returns true if the `handler` argument matches the `handler` previously
/// passed to [setMethodCallHandler].
///
/// This method is useful for tests or test harnesses that want to assert the
/// handler for the specified channel has not been altered by a previous test.
bool checkMethodCallHandler(Future<dynamic> handler(MethodCall call)) => _methodChannelHandlers[this] == handler;
/// Sets a mock callback for intercepting method invocations on this channel.
///
/// The given callback will replace the currently registered mock callback for
......@@ -397,12 +410,20 @@ class MethodChannel {
/// [MethodCodec.encodeSuccessEnvelope], to act as if platform plugin had
/// returned that value.
void setMockMethodCallHandler(Future<dynamic> handler(MethodCall call)) {
_methodChannelMockHandlers[this] = handler;
binaryMessenger.setMockMessageHandler(
name,
handler == null ? null : (ByteData message) => _handleAsMethodCall(message, handler),
);
}
/// Returns true if the `handler` argument matches the `handler` previously
/// passed to [setMockMethodCallHandler].
///
/// This method is useful for tests or test harnesses that want to assert the
/// handler for the specified channel has not been altered by a previous test.
bool checkMockMethodCallHandler(Future<dynamic> handler(MethodCall call)) => _methodChannelMockHandlers[this] == handler;
Future<ByteData> _handleAsMethodCall(ByteData message, Future<dynamic> handler(MethodCall call)) async {
final MethodCall call = codec.decodeMethodCall(message);
try {
......
......@@ -211,9 +211,15 @@ class FakeTextChannel implements MethodChannel {
incoming = handler;
}
@override
bool checkMethodCallHandler(Future<void> Function(MethodCall call) handler) => throw UnimplementedError();
@override
void setMockMethodCallHandler(Future<void> Function(MethodCall call) handler) => throw UnimplementedError();
@override
bool checkMockMethodCallHandler(Future<void> Function(MethodCall call) handler) => throw UnimplementedError();
void validateOutgoingMethodCalls(List<MethodCall> calls) {
expect(outgoingCalls.length, calls.length);
bool hasError = false;
......
......@@ -34,4 +34,26 @@ void main() {
});
expect(count, equals(1));
});
test('can check the handler', () {
Future<ByteData> handler(ByteData call) => Future<ByteData>.value(null);
final BinaryMessenger messenger = ServicesBinding.instance.defaultBinaryMessenger;
expect(messenger.checkMessageHandler('test_channel', null), true);
expect(messenger.checkMessageHandler('test_channel', handler), false);
messenger.setMessageHandler('test_channel', handler);
expect(messenger.checkMessageHandler('test_channel', handler), true);
messenger.setMessageHandler('test_channel', null);
});
test('can check the mock handler', () {
Future<ByteData> handler(ByteData call) => Future<ByteData>.value(null);
final BinaryMessenger messenger = ServicesBinding.instance.defaultBinaryMessenger;
expect(messenger.checkMockMessageHandler('test_channel', null), true);
expect(messenger.checkMockMessageHandler('test_channel', handler), false);
messenger.setMockMessageHandler('test_channel', handler);
expect(messenger.checkMockMessageHandler('test_channel', handler), true);
messenger.setMockMessageHandler('test_channel', null);
});
}
......@@ -58,6 +58,7 @@ void main() {
final String result = await channel.invokeMethod('sayHello', 'hello');
expect(result, equals('hello world'));
});
test('can invoke list method and get result', () async {
ServicesBinding.instance.defaultBinaryMessenger.setMockMessageHandler(
'ch7',
......@@ -89,7 +90,6 @@ void main() {
expect(await channel.invokeListMethod<String>('sayHello', 'hello'), null);
});
test('can invoke map method and get result', () async {
ServicesBinding.instance.defaultBinaryMessenger.setMockMessageHandler(
'ch7',
......@@ -143,6 +143,7 @@ void main() {
fail('PlatformException expected');
}
});
test('can invoke unimplemented method', () async {
ServicesBinding.instance.defaultBinaryMessenger.setMockMessageHandler(
'ch7',
......@@ -158,6 +159,7 @@ void main() {
fail('MissingPluginException expected');
}
});
test('can invoke unimplemented method (optional)', () async {
ServicesBinding.instance.defaultBinaryMessenger.setMockMessageHandler(
'ch8',
......@@ -166,6 +168,7 @@ void main() {
final String result = await optionalMethodChannel.invokeMethod<String>('sayHello', 'hello');
expect(result, isNull);
});
test('can handle method call with no registered plugin', () async {
channel.setMethodCallHandler(null);
final ByteData call = jsonMethod.encodeMethodCall(const MethodCall('sayHello', 'hello'));
......@@ -175,6 +178,7 @@ void main() {
});
expect(envelope, isNull);
});
test('can handle method call of unimplemented method', () async {
channel.setMethodCallHandler((MethodCall call) async {
throw MissingPluginException();
......@@ -186,6 +190,7 @@ void main() {
});
expect(envelope, isNull);
});
test('can handle method call with successful result', () async {
channel.setMethodCallHandler((MethodCall call) async => '${call.arguments}, world');
final ByteData call = jsonMethod.encodeMethodCall(const MethodCall('sayHello', 'hello'));
......@@ -195,6 +200,7 @@ void main() {
});
expect(jsonMethod.decodeEnvelope(envelope), equals('hello, world'));
});
test('can handle method call with expressive error result', () async {
channel.setMethodCallHandler((MethodCall call) async {
throw PlatformException(code: 'bad', message: 'sayHello failed', details: null);
......@@ -214,6 +220,7 @@ void main() {
fail('PlatformException expected');
}
});
test('can handle method call with other error result', () async {
channel.setMethodCallHandler((MethodCall call) async {
throw ArgumentError('bad');
......@@ -233,6 +240,26 @@ void main() {
fail('PlatformException expected');
}
});
test('can check the handler', () {
Future<dynamic> handler(MethodCall call) => Future<dynamic>.value(null);
const MethodChannel channel = MethodChannel('test_handler');
expect(channel.checkMethodCallHandler(null), true);
expect(channel.checkMethodCallHandler(handler), false);
channel.setMethodCallHandler(handler);
expect(channel.checkMethodCallHandler(handler), true);
});
test('can check the mock handler', () {
Future<dynamic> handler(MethodCall call) => Future<dynamic>.value(null);
const MethodChannel channel = MethodChannel('test_handler');
expect(channel.checkMockMethodCallHandler(null), true);
expect(channel.checkMockMethodCallHandler(handler), false);
channel.setMockMethodCallHandler(handler);
expect(channel.checkMockMethodCallHandler(handler), true);
});
});
group('EventChannel', () {
const MessageCodec<dynamic> jsonMessage = JSONMessageCodec();
......
......@@ -266,9 +266,16 @@ class FakeTextChannel implements MethodChannel {
incoming = handler;
}
@override
bool checkMethodCallHandler(Future<void> Function(MethodCall call) handler) => throw UnimplementedError();
@override
void setMockMethodCallHandler(Future<void> Function(MethodCall call) handler) => throw UnimplementedError();
@override
bool checkMockMethodCallHandler(Future<void> Function(MethodCall call) handler) => throw UnimplementedError();
void validateOutgoingMethodCalls(List<MethodCall> calls) {
expect(outgoingCalls.length, calls.length);
bool hasError = false;
......
......@@ -134,10 +134,20 @@ class TestDefaultBinaryMessenger extends BinaryMessenger {
delegate.setMessageHandler(channel, handler);
}
@override
bool checkMessageHandler(String channel, MessageHandler handler) {
return delegate.checkMessageHandler(channel, handler);
}
@override
void setMockMessageHandler(String channel, MessageHandler handler) {
delegate.setMockMessageHandler(channel, handler);
}
@override
bool checkMockMessageHandler(String channel, MessageHandler handler) {
return delegate.checkMockMessageHandler(channel, handler);
}
}
/// Base class for bindings used by widgets library tests.
......
......@@ -52,20 +52,14 @@ class TestTextInput {
register();
}
/// Installs this object as a mock handler for [SystemChannels.textInput].
void register() {
SystemChannels.textInput.setMockMethodCallHandler(_handleTextInputCall);
_isRegistered = true;
}
void register() => SystemChannels.textInput.setMockMethodCallHandler(_handleTextInputCall);
/// Removes this object as a mock handler for [SystemChannels.textInput].
///
/// After calling this method, the channel will exchange messages with the
/// Flutter engine. Use this with [FlutterDriver] tests that need to display
/// on-screen keyboard provided by the operating system.
void unregister() {
SystemChannels.textInput.setMockMethodCallHandler(null);
_isRegistered = false;
}
void unregister() => SystemChannels.textInput.setMockMethodCallHandler(null);
/// Log for method calls.
///
......@@ -76,12 +70,13 @@ class TestTextInput {
/// Whether this [TestTextInput] is registered with [SystemChannels.textInput].
///
/// Use [register] and [unregister] methods to control this value.
// TODO(dnfield): This is unreliable. https://github.com/flutter/flutter/issues/47180
bool get isRegistered => _isRegistered;
bool _isRegistered = false;
bool get isRegistered => SystemChannels.textInput.checkMockMethodCallHandler(_handleTextInputCall);
/// Whether there are any active clients listening to text input.
bool get hasAnyClients => _client > 0;
bool get hasAnyClients {
assert(isRegistered);
return _client > 0;
}
int _client = 0;
......@@ -122,11 +117,15 @@ class TestTextInput {
}
/// Whether the onscreen keyboard is visible to the user.
bool get isVisible => _isVisible;
bool get isVisible {
assert(isRegistered);
return _isVisible;
}
bool _isVisible = false;
/// Simulates the user changing the [TextEditingValue] to the given value.
void updateEditingValue(TextEditingValue value) {
assert(isRegistered);
// Not using the `expect` function because in the case of a FlutterDriver
// test this code does not run in a package:test test zone.
if (_client == 0)
......@@ -149,6 +148,7 @@ class TestTextInput {
/// - User pressed the home button and sent the application to background.
/// - User closed the virtual keyboard.
void closeConnection() {
assert(isRegistered);
// Not using the `expect` function because in the case of a FlutterDriver
// test this code does not run in a package:test test zone.
if (_client == 0)
......@@ -167,6 +167,7 @@ class TestTextInput {
/// Simulates the user typing the given text.
void enterText(String text) {
assert(isRegistered);
updateEditingValue(TextEditingValue(
text: text,
));
......@@ -176,6 +177,7 @@ class TestTextInput {
/// Does not check that the [TextInputAction] performed is an acceptable one
/// based on the `inputAction` [setClientArgs].
Future<void> receiveAction(TextInputAction action) async {
assert(isRegistered);
return TestAsyncUtils.guard(() {
// Not using the `expect` function because in the case of a FlutterDriver
// test this code does not run in a package:test test zone.
......@@ -215,6 +217,7 @@ class TestTextInput {
/// Simulates the user hiding the onscreen keyboard.
void hide() {
assert(isRegistered);
_isVisible = false;
}
}
......@@ -118,8 +118,7 @@ class _PlatformBinaryMessenger extends BinaryMessenger {
}
@override
void setMessageHandler(
String channel, Future<ByteData> Function(ByteData message) handler) {
void setMessageHandler(String channel, MessageHandler handler) {
if (handler == null)
_handlers.remove(channel);
else
......@@ -129,12 +128,21 @@ class _PlatformBinaryMessenger extends BinaryMessenger {
});
}
@override
bool checkMessageHandler(String channel, MessageHandler handler) => _handlers[channel] == handler;
@override
void setMockMessageHandler(
String channel, Future<ByteData> Function(ByteData message) handler) {
throw FlutterError(
'Setting mock handlers is not supported on the platform side.');
}
@override
bool checkMockMessageHandler(String channel, MessageHandler handler) {
throw FlutterError(
'Setting mock handlers is not supported on the platform side.');
}
}
/// The default [BinaryMessenger] for Flutter Web plugins.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment