Flutter for OpenHarmony:mockito 单元测试的替身演员,轻松模拟复杂依赖(测试驱动开发必备) 深度解析与鸿蒙适配指南

Flutter for OpenHarmony:mockito 单元测试的替身演员,轻松模拟复杂依赖(测试驱动开发必备) 深度解析与鸿蒙适配指南

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net

在这里插入图片描述

前言

在软件开发中,单元测试(Unit Testing)是保证代码质量的基石。然而,在测试某个具体的业务逻辑(如 UserService)时,我们往往会遇到各种外部依赖,比如数据库查询、网络请求、设备传感器等。

如果直接调用真实的 DatabaseHttpClient,不仅测试速度慢,而且容易因为网络抖动或环境问题导致测试失败。此外,我们很难复现一些极端场景(如 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 类。

@GenerateMocks

生成

使用

验证

RealClass.dart

build_runner

RealClass.mocks.dart

单元测试

Mock 对象

1.3 核心操作三部曲

  1. Stubbing (桩): when(mock.method()).thenReturn(value) —— “当调用这个方法时,请返回这个值”。
  2. Execution (执行): 运行被测代码,被测代码会调用 Mock 对象。
  3. 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.dartimport'dart:convert';import'package:http/http.dart'as http;classUserApi{finalhttp.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){returnjsonDecode(response.body)['name'];}else{throwException('Failed to load user');}}}

2.3 生成 Mock 类

创建一个测试文件,使用注解 @GenerateMocks

// user_api_test.dartimport'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';@GenerateMocks([http.Client])voidmain(){// ...}

然后在终端运行命令生成代码:

flutter pub run build_runner build 

2.4 编写测试用例

voidmain(){ 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 OKwhen(mockClient.get(Uri.parse('https://api.example.com/users/1'))).thenAnswer((_)async=>http.Response('{"name": "张三"}',200));// 2. Executionfinal 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: 模拟返回 404when(mockClient.get(Uri.parse('https://api.example.com/users/1'))).thenAnswer((_)async=>http.Response('Not Found',404));// 2. Execution & Verificationexpect(userApi.fetchUserName(1), throwsException);});}
在这里插入图片描述


在这里插入图片描述

三、进阶技巧

3.1 参数匹配 (Argument Matchers)

有时候我们不关心具体的参数值,只关心参数类型。

// 匹配任何 Uriwhen(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';classBatteryService{staticconst platform =MethodChannel('ohos.samples.battery');Future<String>getBatteryLevel()async{try{final int result =await platform.invokeMethod('getBatteryLevel');return'当前电量 $result%';}onPlatformExceptioncatch(e){return"获取电量失败: '${e.message}'.";}}}

4.2 编写测试

Flutter 官方提供了 TestDefaultBinaryMessenger 来模拟底层消息。但在单元测试层面,mockito 也可以派上用场,或者我们可以直接 mock 对 BatteryService 的依赖。

这里我们展示如何使用 mockito 测试一个依赖 BatteryServiceViewModel

// battery_view_model.dartclassBatteryViewModel{finalBatteryService _service;String status ='未知';BatteryViewModel(this._service);Future<void>refresh()async{ status =await _service.getBatteryLevel();}}// battery_view_model_test.dart@GenerateMocks([BatteryService])import'battery_view_model_test.mocks.dart';voidmain(){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 都能帮你轻松搞定。

最佳实践

  1. 多用接口:针对 Interface 编程,而不是针对 Implementation。Mock 接口比 Mock 具体类更容易且风险更小。
  2. 不要 Mock 数据类:对于 DTO(Data Transfer Object)或简单的实体类,直接 new 一个真的对象,不要 Mock 它。
  3. 保持测试简单:如果你的 Mock 配置(when…then…)写了几十行,说明被测代码可能耦合太重,需要重构了。

六、完整实战示例:带超时的网络请求模拟器

import'package:mockito/mockito.dart';import'package:test/test.dart';// 1. 业务接口abstractclassNetworkService{Future<String>request(String path);}// 2. 模拟生成的 Mock 类classMockNetworkServiceextendsMockimplementsNetworkService{@overrideFuture<String>request(String? path)=>super.noSuchMethod(Invocation.method(#request,[path]), returnValue:Future.value(""),)asFuture<String>;}// 3. 业务逻辑类classDataManager{finalNetworkService _service;DataManager(this._service);Future<String>safeRequest()async{try{// 模拟 2 秒延迟外的逻辑returnawait _service.request('/data').timeout(Duration(seconds:1));}catch(e){return"Timeout or Error";}}}voidmain(){test('模拟请求超时场景',()async{final mockService =MockNetworkService();final manager =DataManager(mockService);// Stubbing: 让请求延迟 2 秒返回,从而触发业务层的超时when(mockService.request(any)).thenAnswer((_)async{awaitFuture.delayed(Duration(seconds:2));return"Delayed Data";});final result =await manager.safeRequest();expect(result,"Timeout or Error");verify(mockService.request('/data')).called(1);});}
在这里插入图片描述

Read more

诺奖得主辛顿最新访谈:1 万个 AI 可以瞬间共享同一份“灵魂”,这就是为什么人类注定被超越

诺奖得主辛顿最新访谈:1 万个 AI 可以瞬间共享同一份“灵魂”,这就是为什么人类注定被超越

当宇宙级的“嘴炮”遇到降维打击。 编译 | 王启隆 来源 | youtu.be/l6ZcFa8pybE 出品丨AI 科技大本营(ID:rgznai100) 打开最新一期知名播客 StarTalk 的 YouTube 评论区,最高赞的一条留言是这样写的: “我长这么大,第一次看到尼尔·德葛司·泰森(Neil deGrasse Tyson)在一档节目里几乎全程闭嘴,像个手足无措的小学生一样乖乖听讲。” 作为全美最知名的天体物理学家,泰森平时的画风是充满激情、喋喋不休、用宇宙的宏大来震撼嘉宾。但这一次,坐在他对面的那位满头银发、带着温和英音的英国老人,仅仅用最平淡的语气,就让整个演播室陷入了数次令人窒息的沉默。 这位老人是 Geoffrey Hinton。深度学习三巨头之一,2024 年诺贝尔物理学奖得主,被公认为“AI 教父”。 对经常阅读 Hinton 演讲的我来说,这也是比较新奇的一幕—

By Ne0inhk
48小时“烧光”56万!三人创业团队濒临破产,仅因Gemini API密钥被盗:“AI账单远超我们的银行余额”

48小时“烧光”56万!三人创业团队濒临破产,仅因Gemini API密钥被盗:“AI账单远超我们的银行余额”

整理 | 苏宓 出品 | ZEEKLOG(ID:ZEEKLOGnews) 「仅过了 48 小时,一笔 8.2 万美元的天价费用凭空出现,较这家小型初创公司的正常月费暴涨近 46000%。」 这不是假设的虚幻故事,而是一家墨西哥初创公司正在经历的真实危机。 近日,一位名为 RatonVaquero 的开发者在 Reddit 发帖求助称,由于他的 Gemini API 密钥被盗用,原本每月仅约 180 美元(约 1242 元)的费用,在短短 48 小时内暴涨到 82,314.44 美元(约 56.8 万元)。对于这家只有三名开发者的小型创业团队来说,这笔突如其来的账单,几乎等同于灭顶之灾。 “我现在整个人都处在震惊和恐慌之中。”RatonVaquero

By Ne0inhk
曝Windows 12将于今年发布?以AI为核心、NPU成「硬件门槛」,网友吐槽:“不想要的全塞进来了”

曝Windows 12将于今年发布?以AI为核心、NPU成「硬件门槛」,网友吐槽:“不想要的全塞进来了”

整理 | 郑丽媛 出品 | ZEEKLOG(ID:ZEEKLOGnews) 当年,微软一句“Windows 10 将是最后一个版本”的表态,让不少用户以为 Windows 进入了“只更新、不换代”的时代。但几年过去,现实却完全不同。 在 Windows 11 发布之后,如今关于 Windows 12 的传闻再次密集出现。从内部代号、代码片段,到硬件厂商的暗示与 OEM 预热标签,种种线索拼在一起,勾勒出一个明显的趋势——这不会只是一次常规升级,而更像是一次围绕 AI 的平台级重构。 更关键的是,这次争议,可能远比当年 TPM 2.0 更大。 精准卡位 Windows 10 退场的时间?

By Ne0inhk
假网站排全网第二,真官网翻五页都找不到!NanoClaw创始人破防:SEO之战,我快要输了

假网站排全网第二,真官网翻五页都找不到!NanoClaw创始人破防:SEO之战,我快要输了

整理 | 苏宓 出品 | ZEEKLOG(ID:ZEEKLOGnews) 自从 OpenClaw 爆火之后,各种“Claw”项目接连出现,其中以安全优化版 NanoClaw 最为知名。它的核心代码仅有 4000 行,却获得了 AI 大牛 Andrej Karpathy 的点赞。 可谁也没想到,这款口碑极佳的开源项目,近来竟被一个仿冒网站抢了风头。 投诉无门之下,NanoClaw 创始人 Gavriel Cohen 在 X 社交平台上无奈发文怒斥:谷歌搜索错误地将假网站排在真官网前面,不仅破坏了项目声誉,还埋下了严重的安全隐患,而他费尽心力,却只能哀叹一句——“我正在为自己的开源项目打 SEO 战,但我快要输了。” 那么,NanoClaw 究竟发生了什么?又是怎么走红的?事情还要从 OpenClaw

By Ne0inhk