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

用 DeepSeek 打造你的超强代码助手

用 DeepSeek 打造你的超强代码助手

DeepSeek Engineer 是啥? 简单来说,DeepSeek Engineer 是一个基于命令行的智能助手。它能帮你完成这些事: * 快速读文件内容:比如你有个配置文件,直接用命令把它加载进助手,后续所有操作都可以基于这个文件。 * 自动改文件:它不仅能提建议,还可以直接生成差异表(diff),甚至自动应用修改。 * 智能代码生成:比如你让它生成代码片段,它会按照指定格式和规则直接返回。 更重要的是,这一切都是通过 DeepSeek 的强大 API 来实现的。想象一下,你有个贴身助手,不仅能听懂你的代码需求,还能直接动手帮你写! 核心功能拆解 我们先来看 DeepSeek Engineer 的几个核心能力,让你更好地理解它的强大之处。 1. 自动配置 DeepSeek 客户端 启动这个工具时,你只需要准备一个 .env 文件,里面写上你的 API Key,比如: DEEPSEEK_API_

By Ne0inhk
解锁DeepSeek潜能:Docker+Ollama打造本地大模型部署新范式

解锁DeepSeek潜能:Docker+Ollama打造本地大模型部署新范式

🐇明明跟你说过:个人主页 🏅个人专栏:《深度探秘:AI界的007》 🏅 🔖行路有良友,便是天堂🔖 目录 一、引言 1、什么是Docker 2、什么是Ollama 二、准备工作 1、操作系统 2、镜像准备 三、安装 1、安装Docker 2、启动Ollama 3、拉取Deepseek大模型 4、启动Deepseek  一、引言 1、什么是Docker Docker:就像一个“打包好的App” 想象一下,你写了一个很棒的程序,在自己的电脑上运行得很好。但当你把它发给别人,可能会遇到各种问题: * “这个软件需要 Python 3.8,但我只有 Python 3.6!

By Ne0inhk
深挖 DeepSeek 隐藏玩法·智能炼金术2.0版本

深挖 DeepSeek 隐藏玩法·智能炼金术2.0版本

前引:屏幕前的你还在AI智能搜索框这样搜索吗?“这道题怎么写”“苹果为什么红”“怎么不被发现翘课” ,。看到此篇文章的小伙伴们!请准备好你的思维魔杖,开启【霍格沃茨模式】,看我如何更新秘密的【知识炼金术】,我们一起来解锁更加刺激的剧情!友情提醒:《《《前方高能》》》 目录 在哪使用DeepSeek 如何对提需求  隐藏玩法总结 几个高阶提示词 职场打工人 自媒体创作 电商实战 程序员开挂 非适用场地 “服务器繁忙”如何解决 (1)硅基流动平台 (2)Chatbox + API集成方案 (3)各大云平台 搭建个人知识库 前置准备 下载安装AnythingLLM 选择DeepSeek作为AI提供商 创作工作区 导入文档 编辑  编辑 小编寄语 ——————————————————————————————————————————— 在哪使用DeepSeek 我们解锁剧情前,肯定要知道在哪用DeepSeek!咯,为了照顾一些萌新朋友,它的下载方式我放在下面了,拿走不谢!  (1)

By Ne0inhk
【AI大模型】DeepSeek + 通义万相高效制作AI视频实战详解

【AI大模型】DeepSeek + 通义万相高效制作AI视频实战详解

目录 一、前言 二、AI视频概述 2.1 什么是AI视频 2.2 AI视频核心特点 2.3 AI视频应用场景 三、通义万相介绍 3.1 通义万相概述 3.1.1 什么是通义万相 3.2 通义万相核心特点 3.3 通义万相技术特点 3.4 通义万相应用场景 四、DeepSeek + 通义万相制作AI视频流程 4.1 DeepSeek + 通义万相制作视频优势 4.1.1 DeepSeek 优势 4.1.2 通义万相视频生成优势 4.2

By Ne0inhk