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
问题原因
- MethodChannel名称错误:
SystemNavigator.pop实际使用的MethodChannel名称是flutter/system_navigator,而非代码中写的SystemNavigator,导致无法捕获方法调用。 - Platform属性未正确模拟:测试环境中
Platform.isAndroid/Platform.isIOS的取值依赖defaultTargetPlatform,未显式设置的话无法精准控制测试场景,且直接依赖平台属性会让测试无法覆盖多平台分支。 - 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
相关产品推荐
相关产品推荐

