How can I mock/stub out a Flutter platform channel/plugin?
Solution 1
You can use setMockMethodCallHandler to register a mock handler for the underlying method channel:
https://docs.flutter.io/flutter/services/MethodChannel/setMockMethodCallHandler.html
final List<MethodCall> log = <MethodCall>[];
MethodChannel channel = const MethodChannel('plugins.flutter.io/url_launcher');
// Register the mock handler.
channel.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(methodCall);
});
await launch("http://example.com/");
expect(log, equals(<MethodCall>[new MethodCall('launch', "http://example.com/")]));
// Unregister the mock handler.
channel.setMockMethodCallHandler(null);
Solution 2
MethodChannel#setMockMethodCallHandler
is deprecated and removed as of now.
Looks like this is the way to go now:
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
void mockUrlLauncher() {
const channel = MethodChannel('plugins.flutter.io/url_launcher');
handler(MethodCall methodCall) async {
if (methodCall.method == 'yourMethod') {
return 42;
}
return null;
}
TestWidgetsFlutterBinding.ensureInitialized();
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, handler);
}
The details are on GitHub.
And here is a tested example for package_info
plugin for future references:
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
void mockPackageInfo() {
const channel = MethodChannel('plugins.flutter.io/package_info');
handler(MethodCall methodCall) async {
if (methodCall.method == 'getAll') {
return <String, dynamic>{
'appName': 'myapp',
'packageName': 'com.mycompany.myapp',
'version': '0.0.1',
'buildNumber': '1'
};
}
return null;
}
TestWidgetsFlutterBinding.ensureInitialized();
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, handler);
}
Solution 3
When you create a plugin, you are automatically provided a default test:
void main() {
const MethodChannel channel = MethodChannel('my_plugin');
setUp(() {
channel.setMockMethodCallHandler((MethodCall methodCall) async {
return '42';
});
});
tearDown(() {
channel.setMockMethodCallHandler(null);
});
test('getPlatformVersion', () async {
expect(await MyPlugin.platformVersion, '42');
});
}
Let me add some notes about it:
- Calling
setMockMethodCallHandler
allows you to bypass whatever the actual plugin does and return your own value. - You can differentiate methods using
methodCall.method
, which is a string of the called method name. - For plugin creators this is a way to verify the public API names, but it does not test the functionality of the API. You need to use integration tests for that.
Comments
-
matanlurey over 1 year
I read the introduction to platform-specific plugins/channels on the Flutter website and I browsed some simple examples of a plugin, like
url_launcher
:// Copyright 2017 The Chromium 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'; const _channel = const MethodChannel('plugins.flutter.io/url_launcher'); /// Parses the specified URL string and delegates handling of it to the /// underlying platform. /// /// The returned future completes with a [PlatformException] on invalid URLs and /// schemes which cannot be handled, that is when [canLaunch] would complete /// with false. Future<Null> launch(String urlString) { return _channel.invokeMethod( 'launch', urlString, ); }
In widgets tests or integration tests, how can I mock out or stub channels so I don't have to rely on the real device (running Android or iOS) say, actually launching a URL?