Functionality to check handlers set on platform channels (#57696)
This commit is contained in:
parent
cdef67797c
commit
02b1080101
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user