How can I mock/stub out a Flutter platform channel/plugin?

6,511

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.
Share:
6,511
matanlurey
Author by

matanlurey

@matanlurey

Updated on December 02, 2022

Comments

  • matanlurey
    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?