Open benzsuankularb opened 4 years ago
Was facing the same issue, and ended up being able to implement a solution very similar to the one used here: https://github.com/flutter/plugins/blob/4290d18f0e43288087b3754c41d4739872d9a152/packages/webview_flutter/test/webview_flutter_test.dart.
The _FakePlatformViewsController
and FakePlatformWebView
classes are where the magic happens; several of the methods on FakePlatformWebView
use ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage
to simulate the native-to-flutter method invocation.
For your example, it'd be something like this (roughly):
// tests/main_test.dart
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() {
SystemChannels.platform_views.setMockMethodCallHandler(fakePlatformViewsController.fakePlatformViewsMethodHandler);
});
setUp(() {
fakePlatformViewsController.reset();
});
testWidgets("call from native", (tester) async {
tester.pumpWidget(PluginView());
final pluginView = fakePlatformViewsController.lastCreatedView;
await pluginView.fakeSomethingCall("hello");
// Make some assertions here
});
}
class _FakePlatformViewsController {
FakePluginView lastCreatedView;
Future<dynamic> fakePlatformViewsMethodHandler(MethodCall call) {
switch (call.method) {
case 'create':
final args = call.arguments as Map<Object, Object>;
lastCreatedView = FakePluginView(
args['id'] as int,
);
return Future<int>.sync(() => 1);
default:
return Future<void>.sync(() {});
}
}
void reset() {
lastCreatedView = null;
}
}
class FakePluginView {
FakePluginView(int id) {
channel = MethodChannel('core.super_router_$id');
channel.setMockMethodCallHandler(onMethodCall);
}
MethodChannel channel;
Future<Object> onMethodCall(MethodCall call) {
switch (call.method) {
case "something":
return Future<void>.sync(() {});
default:
return Future<void>.sync(() {});
}
}
Future<void> fakeSomethingCall(String someArgumentValue) {
const codec = StandardMethodCodec();
final data = codec.encodeMethodCall(MethodCall('something', {"someArgumentName": someArgument}));
return ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
channel.name,
data,
(ByteData data) {},
);
}
}
Still very much supportive of the feature request, though - the above approach is a lot of faff and it'd be much nicer to have a simple API for testing these kind of method invocations.
from @garry-jeromson answer, add extension below
extension MethodChannelMock on MethodChannel {
Future<void> invokeMockMethod(String method, dynamic arguments) async {
const codec = StandardMethodCodec();
final data = codec.encodeMethodCall(MethodCall(method, arguments));
return ServicesBinding.instance?.defaultBinaryMessenger
.handlePlatformMessage(
name,
data,
(ByteData? data) {},
);
}
}
then just use invokeMockMethod
instead of invokeMethod
test("call from native", () async {
_channel.invokeMockMethod("something");
});
@CyrilHu answer works perfect for me!
extension MethodChannelMock on MethodChannel { Future<void> invokeMockMethod(String method, dynamic arguments) async { const codec = StandardMethodCodec(); final data = codec.encodeMethodCall(MethodCall(method, arguments)); return ServicesBinding.instance?.defaultBinaryMessenger .handlePlatformMessage( name, data, (ByteData? data) {}, ); } }
Revised version with new class:
extension MethodChannelMock on MethodChannel {
Future<void> invokeMockMethod(String method, [dynamic arguments]) async {
const codec = StandardMethodCodec();
final data = codec.encodeMethodCall(MethodCall(method, arguments));
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.handlePlatformMessage(name, data, (data) {});
}
}
Thanks @CyrilHu.
extension MethodChannelMock on MethodChannel { Future<void> invokeMockMethod(String method, dynamic arguments) async { const codec = StandardMethodCodec(); final data = codec.encodeMethodCall(MethodCall(method, arguments)); return ServicesBinding.instance?.defaultBinaryMessenger .handlePlatformMessage( name, data, (ByteData? data) {}, ); } }
Revised version with new class:
extension MethodChannelMock on MethodChannel { Future<void> invokeMockMethod(String method, [dynamic arguments]) async { const codec = StandardMethodCodec(); final data = codec.encodeMethodCall(MethodCall(method, arguments)); TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.handlePlatformMessage(name, data, (data) {}); } }
Thanks @CyrilHu.
@jm-marquez-humanitcare the revised version loses track of the asynchronous events, i guess it would be better to 'await' the last line:
extension MethodChannelMock on MethodChannel {
Future<void> invokeMockMethod(String method, [dynamic arguments]) async {
const codec = StandardMethodCodec();
final data = codec.encodeMethodCall(MethodCall(method, arguments));
await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.handlePlatformMessage(name, data, (data) {});
}
}
extension MethodChannelMock on MethodChannel { Future<void> invokeMockMethod(String method, dynamic arguments) async { const codec = StandardMethodCodec(); final data = codec.encodeMethodCall(MethodCall(method, arguments)); return ServicesBinding.instance?.defaultBinaryMessenger .handlePlatformMessage( name, data, (ByteData? data) {}, ); } }
Revised version with new class:
extension MethodChannelMock on MethodChannel { Future<void> invokeMockMethod(String method, [dynamic arguments]) async { const codec = StandardMethodCodec(); final data = codec.encodeMethodCall(MethodCall(method, arguments)); TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.handlePlatformMessage(name, data, (data) {}); } }
Thanks @CyrilHu.
@jm-marquez-humanitcare the revised version loses track of the asynchronous events, i guess it would be better to 'await' the last line:
extension MethodChannelMock on MethodChannel { Future<void> invokeMockMethod(String method, [dynamic arguments]) async { const codec = StandardMethodCodec(); final data = codec.encodeMethodCall(MethodCall(method, arguments)); await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.handlePlatformMessage(name, data, (data) {}); } }
@SlavikHaltia - or, allow await control from the caller:
extension MethodChannelMock on MethodChannel {
Future<ByteData?> invokeMockMethod(String method, [dynamic arguments]) async {
const codec = StandardMethodCodec();
final data = codec.encodeMethodCall(MethodCall(method, arguments));
return TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.handlePlatformMessage(name, data, (ByteData? data) {});
}
}
I'm developing a native plugin and trying to do unit tests. All unit tests will be done in Dart (No native code).
Flutter has a test example of how you can test call method channel from Dart to native using
setMockMethodCallHandler
.The problem is I've not found the way to test method channel that calls from native to Dart that uses
setMethodCallHandler
to handle a call from native.For example,
I'm purpose for method like so
_channel.simulateInvokeMethod("something");