鸿蒙预览场景模拟:@ohos/hamock 模拟框架详解与源码级实践指南
【免费下载链接】cann-recipes-harmony-infer本项目为鸿蒙开发者提供基于CANN平台的业务实践案例,方便开发者参考实现端云能力迁移及端侧推理部署。项目地址: https://gitcode.com/cann/cann-recipes-harmony-infer
Hamock 是 OpenHarmony 生态中的轻量模拟框架,专为 DevEco Studio 预览场景设计,允许开发者在组件预览时通过@MockSetup装饰器重定义组件方法或重赋值组件属性,从而让 UI 预览摆脱对真实业务数据与接口的依赖。本文以当前仓库(cann-recipes-harmony-infer)中 Soble 案例所依赖的 @ohos/hamock 源码包 为蓝本,结合框架底层实现,系统讲解 Hamock 的安装、API 使用、执行原理与约束限制,帮助你在鸿蒙应用开发中快速搭建可独立运行的预览 Mock 环境。
一、Hamock 是什么:预览场景的模拟框架
Hamock 是 OpenHarmony 上针对预览场景(DevEco Studio Previewer)提供的模拟框架。它的核心能力是:让开发者在不修改业务代码的前提下,为 UI 组件指定一组“预览专用的替身数据”——在预览器打开组件时,用 Mock 数据替换掉原本依赖网络请求、原生能力或复杂状态初始化的方法与属性,使页面预览始终可运行、可展示。
它解决的问题非常具体:ArkTS 声明式范式组件在预览时,aboutToAppear、构造参数、@Prop等属性的初始化链路往往依赖外部环境(如模型加载、接口回调),一旦依赖缺失,预览器便无法渲染出完整页面。Hamock 通过@MockSetup装饰器提供了一条“预览专用初始化通道”,在不触碰业务逻辑的前提下注入 Mock 行为。
从当前仓库的依赖配置可以确认 Hamock 的实际接入方式:在 Soble 工程的 oh-package.json5 中,@ohos/hamock以1.0.0版本被声明在devDependencies中(与@ohos/hypium并列),在 oh-package-lock.json5 中锁定了版本与校验信息,说明它作为开发期依赖随工程一并分发。
二、下载安装
Hamock 通过 ohpm(OpenHarmony 包管理器)安装,命令如下:
ohpm install @ohos/hamock安装完成后,工程中会生成对应的锁文件与依赖目录。在本仓库中,安装产物即位于 harmony_infer/harmony_os_next/Soble/oh_modules/@ohos/hamock/ 目录下,其 oh-package.json5 声明了该包的元信息:
name:@ohos/hamockversion:1.0.0description:A mock framework for OpenHarmony application.main:index.ets(入口模块,对外导出MockSetup、MockKit、when、ArgumentMatchers)types:index.d.ts(TypeScript 类型声明,供 IDE 智能提示与静态检查使用)license:Apache-2.0
从 index.ets 可以看到包的导出面:
export { MockSetup, MockKit, when } from './src/main/mock/MockKit'; export { ArgumentMatchers } from './src/main/mock/ArgumentMatchers';也就是说,安装后你在业务代码中实际可用的是四个核心导出:MockSetup(装饰器)、MockKit(模拟器)、when(存根配置入口)与ArgumentMatchers(参数匹配器)。
三、核心概念:@MockSetup 装饰器
@MockSetup用于修饰 Mock 方法,仅支持声明式范式的组件。当开发者预览该组件时,预览运行时将在组件初始化时执行被@MockSetup修饰的方法。开发者可以在这个被修饰的方法内重定义组件的方法,或重赋值组件的属性,这些变更只在预览时生效,不影响真机运行。
说明:
@MockSetup修饰的方法仅在预览场景会自动触发,并先于组件的aboutToAppear执行。
在仓库附带的 MockKit.ts 源码 中可以看到MockSetup的底层实现,它本质上是对组件aboutToAppear生命周期的“前置包装”:
function MockSetup(target: Object, propertyName: string | Symbol, descriptor: TypedPropertyDescriptor<() => void>): void { const aboutToAppearOrigin = target.aboutToAppear; const setup = descriptor.value; target.aboutToAppear = function (...args: any[]) { if (target.__Param) { // copy attributes and params of the original context // 将组件上下文中的属性与参数复制到当前执行上下文 } if (setup) { // apply the mock content setup.apply(this); // 先执行 @MockSetup 修饰的方法 } if (aboutToAppearOrigin) { // append to aboutToAppear function of the original context aboutToAppearOrigin.apply(this, args); // 再执行原始 aboutToAppear } } }这段实现揭示了三个关键设计:
- 执行顺序:
@MockSetup方法先于组件原有aboutToAppear执行,因此 Mock 注入的数据可以在业务初始化逻辑读取之前就位; - 上下文一致性:通过
target.__Param将组件上下文(含@Prop等参数)复制到装饰器方法执行上下文,保证 Mock 方法内访问this.xxx语义正确; - 零侵入:原始
aboutToAppear被保存并追加执行,业务生命周期逻辑不被破坏。
四、使用示例一:Mock UI 组件的方法
在 ArkTS 页面代码中引入 Hamock,在目标组件中定义一个方法并用@MockSetup修饰,在该方法内使用MockKit模拟目标方法:
import { MockKit, when, MockSetup } from '@ohos/hamock'; @Entry @Component struct Index { ... @MockSetup randomName() { let mocker: MockKit = new MockKit(); let mockfunc: Object = mocker.mockFunc(this, this.method1); // mock 指定的方法在指定入参的返回值 when(mockfunc)('test').afterReturn(1); } ... // 业务场景调用方法 const result: number = this.method1('test'); // in previewer, result = 1 }上例中,this.method1('test')在真机上执行真实逻辑,而在预览器中会命中 Mock 存根并返回1。
从 MockKit.ts 的源码看,mockFunc的替换机制是:通过findName找到目标方法在对象上的属性名,然后用包装函数f覆盖originalObject[name],同时把原方法保存在recordMockedMethod映射中以便恢复。包装函数在执行时调用getReturnInfo查询存根表stubs:
- 有匹配的存根(action):执行存根动作并返回结果;
- 无匹配存根:返回
undefined。
每次被调用时,recordMethodCall都会以“方法名(参数列表)”为键记录调用次数,为后续的verify断言提供数据。
when的语义在 ExtendInterface.ts 中实现:when(mockfunc)('test')本质是先通过stub()暂存参数(这里是入参'test'),随后.afterReturn(1)调用stubMockedCall把“入参 → 返回动作”的映射写入 MockKit 的stubs表。框架提供的全部存根行为包括:
| 方法 | 行为 | 典型用途 |
|---|---|---|
afterReturn(value) | 固定返回指定值 | 替代返回固定数据的接口/方法 |
afterReturnNothing() | 返回undefined | 模拟无返回值的调用 |
afterAction(action) | 执行自定义动作函数 | 模拟带副作用的调用 |
afterThrow(msg) | 抛出指定异常信息 | 模拟异常分支 |
五、使用示例二:Mock UI 组件的属性
在 ArkTS 页面代码中引入 Hamock,在目标组件中定义一个方法并用@MockSetup修饰,在该方法内对需要 Mock 的属性重新赋值:
import { MockSetup } from '@ohos/hamock'; @Component struct Person { @Prop species: string; ... // 在 @MockSetup 片段中,定义对象属性 @MockSetup randomName() { this.species = 'primates'; } ... // 业务场景调用属性(如果从初始化到调用期间,该属性无变化) const result: string = this.species; // in previewer, result = primates }上例中,@Prop species在预览时会被赋值为'primates',从而让依赖该属性的 UI 分支能够正常渲染。属性的 Mock 不经过MockKit存根表,而是依赖@MockSetup方法先于aboutToAppear执行的时序:赋值动作发生在业务代码读取属性之前。
六、深入原理:MockKit 的完整 API 与验证机制
除了@MockSetup与when/afterReturn组合,MockKit 还提供了一组面向对象级 Mock 与调用验证的 API,类型声明见 index.d.ts:
6.1 MockKit 核心方法
| 方法 | 说明 |
|---|---|
mockFunc(obj, func) | 将对象上的指定方法替换为 Mock 函数,返回包装函数 |
mockObject(obj) | 浅拷贝对象并将其上所有函数类型成员逐一 Mock,返回 Mock 后的对象副本 |
verify(methodName, argsArray) | 校验某方法以指定参数被调用的次数,返回VerificationMode |
ignoreMock(obj, func) | 还原指定对象的指定方法(忽略 Mock) |
clear(obj) | 还原指定对象上所有被 Mock 过的方法 |
clearAll() | 清空当前 MockKit 实例的全部存根与调用记录 |
其中verify配合 VerificationMode.js 提供次数断言:
export interface VerificationMode { times(count: Number): void // 断言恰好调用 count 次 never(): void // 断言从未调用 once(): void // 断言恰好调用 1 次 atLeast(count: Number): void // 断言至少调用 count 次 atMost(count: Number): void // 断言至多调用 count 次 }6.2 ArgumentMatchers 参数匹配
when(mockfunc)的入参除了字面量,还可以使用参数匹配器,实现“任意值/按类型/按正则”的存根匹配。匹配器定义在 ArgumentMatchers.ts 中:
export class ArgumentMatchers { static any; // 匹配任意入参 static anyString; // 匹配任意字符串 static anyBoolean; // 匹配任意布尔值 static anyNumber; // 匹配任意数字 static anyObj; // 匹配任意对象 static anyFunction; // 匹配任意函数 static matchRegexs(Regex: RegExp): void // 按正则匹配字符串入参 }典型用法:
// 只要入参是任意字符串,就返回 1 when(mockfunc)(ArgumentMatchers.anyString).afterReturn(1); // 入参匹配正则 /^test/ 时返回 2 when(mockfunc)(ArgumentMatchers.matchRegexs(/^test/)).afterReturn(2);从源码实现看,stubApply在写入存根时会把匹配器映射为内部占位键(如"<any String>"),而getReturnInfo在查询时通过matcheReturnKey做类型推导与正则匹配,从而把“匹配器定义”与“按入参查存根”两阶段衔接起来。
6.3 一次完整 Mock 的调用链
综合源码,一次方法 Mock 的完整链路为:
new MockKit()初始化存根表stubs、调用记录表recordCalls、被 Mock 方法记录表recordMockedMethod;mocker.mockFunc(this, this.method1)定位属性名并替换为包装函数,原方法被存档;when(mockfunc)('test')暂存参数,.afterReturn(1)将「入参 → 返回 1 的动作」写入stubs;- 预览器触发组件初始化,
@MockSetup方法执行(先于aboutToAppear),完成上述注册; - 业务代码调用
this.method1('test'),命中包装函数,getReturnInfo查表得到返回 1 的动作并执行,同时记录一次调用; - 如需断言,用
verify('method1', ['test']).once()校验调用次数。
七、约束与限制
根据 Hamock 官方 README(即本仓库中的 hamock README),该框架在以下版本验证通过:
| 组件 | 版本 |
|---|---|
| DevEco Studio | 4.1 (4.1.3.400) |
| SDK | API11 (4.1.0.36) |
MockSetup仅在 API11 支持。因此:
- 若目标工程的 SDK 版本低于 API11,无法使用
@MockSetup装饰器,只能考虑在预览之外的其他测试手段; - 从 CHANGELOG.md 的版本记录看,
1.0.0-rc首次提供 DevEco Studio 预览器场景使能的MockSetup装饰器,1.0.0修复了once断言问题,本仓库锁定的是修复后的1.0.0正式版; - 使用
@MockSetup时,Mock 逻辑仅作用于预览场景,真机运行不受影响,这既是能力也是边界——不能把预览 Mock 当作单元测试替身或运行时数据源。
八、在仓库中的工程化落地
本仓库的 Soble 案例工程 是 Hamock 的典型落地场景:该工程以@ohos/hamock: 1.0.0作为devDependencies依赖,与@ohos/hypium一并用于开发调试(详见 oh-package.json5 与 oh-package-lock.json5)。在涉及 Sobel 图像处理、模型推理等依赖原生能力与算力环境的页面中,预览器无法真正执行端侧推理,此时便适合用 Hamock 在预览场景中注入可展示的模拟数据,保证 UI 开发与调试的独立进行。
需要提醒的是,仓库对oh_modules目录是随依赖安装自动生成的,若在你的工程中复现,请以ohpm install拉取对应版本,而不要手工拷贝本仓库的oh_modules内容。
九、参与贡献与开源协议
Hamock 遵循 Apache License 2.0 开源协议(详见其包声明中的license字段)。若在使用过程中发现缺陷,可以向 OpenHarmony 的 testfwk 相关仓库提交 Issue 或 PR 参与共建。
结语
Hamock 以“预览场景专用”为定位,通过@MockSetup装饰器 +MockKit存根机制,为 ArkTS 声明式组件的 UI 预览提供了一条低成本、零侵入的 Mock 通道。结合其源码实现,我们可以清晰地看到:方法 Mock 基于“替换属性 + 存根表查询 + 调用记录”三件套,属性 Mock 依赖“先于aboutToAppear执行”的生命周期时序,而when/ArgumentMatchers/VerificationMode则分别承担存根配置、灵活匹配与调用验证的职责。掌握这套机制,你就能在鸿蒙应用开发中让预览器“跑得起来、看得见数据”,显著提升 UI 侧开发调试效率。
【免费下载链接】cann-recipes-harmony-infer本项目为鸿蒙开发者提供基于CANN平台的业务实践案例,方便开发者参考实现端云能力迁移及端侧推理部署。项目地址: https://gitcode.com/cann/cann-recipes-harmony-infer
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考