Flutter for OpenHarmony:mockito 单元测试的替身演员,轻松模拟复杂依赖(测试驱动开发必备) 深度解析与鸿蒙适配指南
开源鸿蒙跨平台社区分享单元测试Mockito使用指南,助力开发者高效验证业务逻辑。文章介绍Mockito核心概念,通过创建Mock对象替代真实依赖,实现快速测试。详细讲解集成步骤、基础用法和进阶技巧,包括参数匹配、顺序验证和异常模拟。特别针对OpenHarmony开发场景,演示如何模拟鸿蒙原生通道调用,解决测试环境依赖问题。通过Mockito可显著提升测试效率,确保代码质量后再进行真机集成测试。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

前言
在软件开发中,单元测试(Unit Testing)是保证代码质量的基石。然而,在测试某个具体的业务逻辑(如 UserService)时,我们往往会遇到各种外部依赖,比如数据库查询、网络请求、设备传感器等。
如果直接调用真实的 Database 或 HttpClient,不仅测试速度慢,而且容易因为网络抖动或环境问题导致测试失败。此外,我们很难复现一些极端场景(如 500 服务器错误、数据库连接超时)。
Mockito 就是为了解决这个问题而生的。它允许我们创建对象的 Mock(替身),并精确控制这些替身的行为(Stubbing)和验证它们的交互(Verification)。
在 OpenHarmony 应用开发中,使用 mockito 可以让我们在开发机(Host)上就能快速验证大部分业务逻辑,通过后再部署到鸿蒙真机进行集成测试,极大地提高了开发效率。
一、核心概念与工作原理
1.1 什么是 Mock?
Mock 对象是真实对象的“傀儡”。它继承自真实类,但不会执行真实类的任何代码。相反,它记录了所有对它的调用,并根据你的配置返回预设的值。
1.2 Mockito 的黑魔法:代码生成 (Codegen)
早期版本的 Mockito 依赖 noSuchMethod 和反射(Mirrors),但在 Flutter 中禁止使用反射(为了 AOT 优化)。因此,现在的 mockito 配合 build_runner 使用,在编译时生成 Mock 类。
1.3 核心操作三部曲
- Stubbing (桩):
when(mock.method()).thenReturn(value)—— “当调用这个方法时,请返回这个值”。 - Execution (执行): 运行被测代码,被测代码会调用 Mock 对象。
- Verification (验证):
verify(mock.method())—— “验证这个方法是否被调用过,且参数正确”。
二、集成与基础用法
2.1 添加依赖
mockito 通常只在 dev_dependencies 中使用。
dev_dependencies:
flutter_test:
sdk: flutter
mockito: ^5.6.3
build_runner: ^2.4.0
2.2 定义被测类
假设我们有一个从 API 获取用户信息的服务:
// user_api.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
class UserApi {
final http.Client client;
UserApi(this.client);
Future<String> fetchUserName(int id) async {
final response = await client.get(Uri.parse('https://api.example.com/users/$id'));
if (response.statusCode == 200) {
return jsonDecode(response.body)['name'];
} else {
throw Exception('Failed to load user');
}
}
}
2.3 生成 Mock 类
创建一个测试文件,使用注解 @GenerateMocks。
// user_api_test.dart
import 'package:mockito/annotations.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'user_api.dart';
// 引入生成的文件
import 'user_api_test.mocks.dart';
([http.Client])
void main() {
// ...
}
然后在终端运行命令生成代码:
flutter pub run build_runner build
2.4 编写测试用例
void main() {
late MockClient mockClient;
late UserApi userApi;
setUp(() {
mockClient = MockClient();
userApi = UserApi(mockClient);
});
test('returns name if the http call completes successfully', () async {
// 1. Stubbing: 模拟 http.Client 返回 200 OK
when(mockClient.get(Uri.parse('https://api.example.com/users/1')))
.thenAnswer((_) async => http.Response('{"name": "张三"}', 200));
// 2. Execution
final name = await userApi.fetchUserName(1);
// 3. Verification: 验证结果
expect(name, '张三');
// 验证 get 方法确实被调用了一次
verify(mockClient.get(Uri.parse('https://api.example.com/users/1'))).called(1);
});
test('throws an exception if the http call completes with an error', () {
// 1. Stubbing: 模拟返回 404
when(mockClient.get(Uri.parse('https://api.example.com/users/1')))
.thenAnswer((_) async => http.Response('Not Found', 404));
// 2. Execution & Verification
expect(userApi.fetchUserName(1), throwsException);
});
}


三、进阶技巧
3.1 参数匹配 (Argument Matchers)
有时候我们不关心具体的参数值,只关心参数类型。
// 匹配任何 Uri
when(mockClient.get(any)).thenAnswer(...)
// 命名参数匹配
when(obj.method(foo: anyNamed('foo'))).thenReturn(...)
// 捕获参数进行验证
test('captures arguments', () {
// ... trigger ...
// 验证调用并捕获参数
final captured = verify(mockClient.get(captureAny)).captured;
print(captured.first); // 输出 Uri 对象
});
3.2 顺序验证 (InOrder)
验证多个方法的调用顺序。
test('verify interaction order', () {
final cat = MockCat();
cat.eat();
cat.sleep();
verifyInOrder([
cat.eat(),
cat.sleep(),
]);
});
3.3 模拟异常与重试逻辑
开发鸿蒙应用时,网络波动是常态。我们可以 Mock 抛出异常来测试 App 的重试机制是否工作。
test('测试重试逻辑', () async {
// 第一次调用抛出异常,第二次成功
when(mockApi.getData())
.thenThrow(Exception('Network Error'))
.thenAnswer((_) async => 'Success');
// 调用业务逻辑...
});

四、OpenHarmony 适配与实战:模拟鸿蒙原生通道 (Platform Channel)
在 OpenHarmony 开发中,我们经常需要通过 MethodChannel 调用系统的原生能力(如获取电池电量、调用 HiLog)。在写 UI 测试或逻辑测试时,我们不能真的去调底层,因为测试环境可能只是一个 Dart VM。
我们可以 Mock MethodChannel 的 handlers。
4.1 场景:电池电量获取
假设我们有一个 BatteryService:
import 'package:flutter/services.dart';
class BatteryService {
static const platform = MethodChannel('ohos.samples.battery');
Future<String> getBatteryLevel() async {
try {
final int result = await platform.invokeMethod('getBatteryLevel');
return '当前电量 $result%';
} on PlatformException catch (e) {
return "获取电量失败: '${e.message}'.";
}
}
}
4.2 编写测试
Flutter 官方提供了 TestDefaultBinaryMessenger 来模拟底层消息。但在单元测试层面,mockito 也可以派上用场,或者我们可以直接 mock 对 BatteryService 的依赖。
这里我们展示如何使用 mockito 测试一个依赖 BatteryService 的 ViewModel。
// battery_view_model.dart
class BatteryViewModel {
final BatteryService _service;
String status = '未知';
BatteryViewModel(this._service);
Future<void> refresh() async {
status = await _service.getBatteryLevel();
}
}
// battery_view_model_test.dart
([BatteryService])
import 'battery_view_model_test.mocks.dart';
void main() {
test('ViewModel updates status from service', () async {
final mockService = MockBatteryService();
final viewModel = BatteryViewModel(mockService);
// Stubbing: 模拟鸿蒙设备返回了 100% 电量
when(mockService.getBatteryLevel())
.thenAnswer((_) async => '当前电量 100%');
await viewModel.refresh();
expect(viewModel.status, '当前电量 100%');
});
}
4.3 为什么这对鸿蒙开发重要?
鸿蒙设备的 API(如 @ohos.batteryInfo)只能在真机或模拟器运行。如果你的 Dart 代码中混杂了原生调用,一旦离开真机环境就会报错。
通过 Dependency Injection (依赖注入) 配合 Mockito,你可以让 Dart 业务逻辑与鸿蒙原生 API 解耦。
- 开发时:在 PC 上运行测试,Mock 掉鸿蒙 API,开发效率极高。
- 运行时:注入真实的
BatteryService,调用鸿蒙底层。
五、总结
mockito 是测试驱动开发(TDD)的利器。它让我们能够隔离关注点,专注于当前模块的逻辑,而不必担心外部世界的复杂性。
对于 OpenHarmony 开发者,掌握 Mock 技术意味着你可以构建出架构更清晰、可测试性更强的应用。无论是模拟网络、数据库,还是模拟鸿蒙特有的系统服务,mockito 都能帮你轻松搞定。
最佳实践:
- 多用接口:针对 Interface 编程,而不是针对 Implementation。Mock 接口比 Mock 具体类更容易且风险更小。
- 不要 Mock 数据类:对于 DTO(Data Transfer Object)或简单的实体类,直接 new 一个真的对象,不要 Mock 它。
- 保持测试简单:如果你的 Mock 配置(when…then…)写了几十行,说明被测代码可能耦合太重,需要重构了。
六、完整实战示例:带超时的网络请求模拟器
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
// 1. 业务接口
abstract class NetworkService {
Future<String> request(String path);
}
// 2. 模拟生成的 Mock 类
class MockNetworkService extends Mock implements NetworkService {
Future<String> request(String? path) => super.noSuchMethod(
Invocation.method(#request, [path]),
returnValue: Future.value(""),
) as Future<String>;
}
// 3. 业务逻辑类
class DataManager {
final NetworkService _service;
DataManager(this._service);
Future<String> safeRequest() async {
try {
// 模拟 2 秒延迟外的逻辑
return await _service.request('/data').timeout(Duration(seconds: 1));
} catch (e) {
return "Timeout or Error";
}
}
}
void main() {
test('模拟请求超时场景', () async {
final mockService = MockNetworkService();
final manager = DataManager(mockService);
// Stubbing: 让请求延迟 2 秒返回,从而触发业务层的超时
when(mockService.request(any)).thenAnswer((_) async {
await Future.delayed(Duration(seconds: 2));
return "Delayed Data";
});
final result = await manager.safeRequest();
expect(result, "Timeout or Error");
verify(mockService.request('/data')).called(1);
});
}

更多推荐




所有评论(0)