如何模拟/存根 Flutter 平台通道/插件?

Posted

技术标签:

【中文标题】如何模拟/存根 Flutter 平台通道/插件?【英文标题】:How can I mock/stub out a Flutter platform channel/plugin? 【发布时间】:2017-10-09 09:54:11 【问题描述】:

我在 Flutter 网站上阅读了 introduction to platform-specific plugins/channels 并浏览了一些简单的插件示例,例如 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,
  );

在小部件测试或集成测试中,我如何模拟或存根通道,这样我就不必依赖真实设备(运行 androidios),例如实际启动 URL?

【问题讨论】:

【参考方案1】:

您可以使用 setMockMethodCallHandler 为底层方法通道注册一个模拟处理程序:

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);

【讨论】:

请读者理解,因为MethodChannel是一个规范的(const)对象,即使插件作者没有公开channel,我也可以在自己的包中创建和setMockMethodCallHandler对吧? 插件需要提供一个 @visibleForTesting 构造函数,该构造函数接受 MethodChannel 参数作为使用默认参数的替代方案。 url_launcher 还没有这样做,但是 firebase_analytics 插件就是这种模式的一个例子。最终,我希望大多数插件都遵循这种模式并且更易于测试。 github.com/flutter/firebase_analytics/blob/master/lib/… @CollinJackson,您对该链接有更新吗?还是向导?旧链接已失效。 这里是固定链接github.com/flutter/plugins/blob/master/packages/… 这是另一个最近更新的插件,它有点干净:github.com/flutter/plugins/blob/master/packages/cloud_functions/…【参考方案2】:

MethodChannel#setMockMethodCallHandler 现已弃用并删除。

看起来这是现在要走的路:

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);

详情请见GitHub。

这里是package_info 插件的测试示例供将来参考:

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);

【讨论】:

这应该是 Flutter 2.5 以来接受的答案,更多信息flutter.dev/docs/release/breaking-changes/…【参考方案3】:

当您创建插件时,系统会自动为您提供默认测试:

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');
  );

让我添加一些关于它的注释:

调用setMockMethodCallHandler 允许您绕过实际插件所做的一切并返回您自己的值。 您可以使用methodCall.method 来区分方法,它是被调用方法名称的字符串。 对于插件创建者,这是一种验证公共 API 名称的方法,但它不会测试 API 的功能。您需要为此使用集成测试。

【讨论】:

以上是关于如何模拟/存根 Flutter 平台通道/插件?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Flutter 集成测试中最好地存根/模拟 rest API 调用

测试使用插件和平台通道的 Flutter 代码

如何通过 FlutterView 或 Flutter.createFragment 使用平台通道

如何使用FlutterView或Flutter.createFragment的平台通道

修改Flutter app图标

什么是最好安装的 Flutter 版本,它具有最兼容的插件以及保持在开发或稳定的通道