You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中如何为Mixin编写单元测试?现有测试代码执行报错

Flutter Mixin单元测试问题解决

问题场景

现有一个处理应用退出逻辑的Mixin:

mixin AppCloser {
    void closeApp() {
      if (Platform.isAndroid) {
        SystemNavigator.pop();
      } else if (Platform.isIOS) {
        exit(0);
      }
    }
  }

尝试编写的测试代码如下,但执行后未捕获到预期的方法调用,测试失败:

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();
  test('Test closeApp method', () {
    // Mock the SystemNavigator.pop method
    const MethodChannel methodChannel = MethodChannel('SystemNavigator');
    final List<MethodCall> log = <MethodCall>[];
    methodChannel.setMockMethodCallHandler((MethodCall methodCall) async {
      log.add(methodCall);
    });

    // Determine the expected method call based on the platform
    final expectedMethodCall = defaultTargetPlatform == TargetPlatform.android
        ? 'pop'
        : 'SystemNavigator.pop';

    // Create a class that uses the AppCloser mixin
    final testClass = TestClassWithAppCloser();

    // Call the closeApp method
    testClass.closeApp();

    // Verify that the appropriate method was called based on the platform
    expect(log, <Matcher>[
      isMethodCall(expectedMethodCall, arguments: null),
    ]);
  });
}

// Create a test class that uses the mixin
class TestClassWithAppCloser with AppCloser {}

测试失败输出:

package:matcher                                     expect
package:flutter_test/src/widget_tester.dart 454:18  expect
test/utils/mixins_test.dart 28:5                    main.<fn>

Expected: [<has method name: 'pop' with arguments: <null>>]
  Actual: []
   Which: at location [0] is [] which shorter than expected

问题原因

  1. MethodChannel名称错误:SystemNavigator.pop实际使用的MethodChannel名称是flutter/system_navigator,而非代码中写的SystemNavigator,导致无法捕获方法调用。
  2. Platform属性未正确模拟:测试环境中Platform.isAndroid/Platform.isIOS的取值依赖defaultTargetPlatform,未显式设置的话无法精准控制测试场景,且直接依赖平台属性会让测试无法覆盖多平台分支。
  3. exit方法未拦截:iOS分支的exit(0)会直接终止测试进程,导致后续验证逻辑无法执行。

修正后的测试代码

import 'dart:io';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';

// Mock类用于拦截exit方法
class MockIO extends Mock {
  void Function(int)? exit;
}

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();
  late MockIO mockIO;
  late List<MethodCall> methodCalls;
  late void Function(int) originalExit;

  setUp(() {
    // 初始化模拟对象和方法调用日志
    mockIO = MockIO();
    methodCalls = [];
    // 保存原始exit方法用于恢复
    originalExit = exit;
    // 替换exit方法为模拟实现
    exit = (code) => mockIO.exit!(code);
    // 设置正确的MethodChannel并捕获调用
    const MethodChannel('flutter/system_navigator')
        .setMockMethodCallHandler((call) async {
      methodCalls.add(call);
      return null;
    });
  });

  tearDown(() {
    // 恢复原始exit方法
    exit = originalExit;
    // 重置MethodChannel
    const MethodChannel('flutter/system_navigator').setMockMethodCallHandler(null);
  });

  test('closeApp calls SystemNavigator.pop on Android', () {
    // 强制设置测试环境为Android
    when(defaultTargetPlatform).thenReturn(TargetPlatform.android);
    
    final testClass = TestClassWithAppCloser();
    testClass.closeApp();

    // 验证SystemNavigator.pop被调用
    expect(methodCalls, [isMethodCall('pop', arguments: null)]);
    // 验证exit未被触发
    verifyNever(mockIO.exit!(any));
  });

  test('closeApp calls exit(0) on iOS', () {
    // 强制设置测试环境为iOS
    when(defaultTargetPlatform).thenReturn(TargetPlatform.iOS);
    
    final testClass = TestClassWithAppCloser();
    testClass.closeApp();

    // 验证exit(0)被调用
    verify(mockIO.exit!(0)).called(1);
    // 验证SystemNavigator.pop未被触发
    expect(methodCalls, isEmpty);
  });
}

class TestClassWithAppCloser with AppCloser {}

补充说明

  • 需要在pubspec.yaml中添加依赖:
    dev_dependencies:
      mockito: ^5.4.0
      build_runner: ^2.4.0
    
  • 若不想依赖mockito,可手动替换exit方法实现:
    // setUp中替换exit
    setUp(() {
      originalExit = exit;
      exit = (code) {
        // 仅记录exit调用,不终止进程
        exitCode = code;
      };
    });
    
    tearDown(() {
      exit = originalExit;
    });
    

内容的提问来源于stack exchange,提问作者Shweta Chauhan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 16:53:19