测试工具基座 (core/testing)
介绍
@core/testing 模块提供测试环境下的依赖注入容器管理工具。基于 @core/di 的子容器机制,实现:
- 测试隔离:每个测试用例使用独立的子容器,避免测试之间相互影响
- Mock 注入:在子容器中注册 Mock 服务,覆盖父容器的真实实现
- 零侵入:测试结束后恢复原始容器,不影响其他模块的服务注册
核心原理
利用 ServiceContainer 的父子容器链式查找机制:
setup()基于当前全局容器创建子容器,子容器继承父容器的所有服务registerMock()在子容器中注册 Mock 服务,优先级高于父容器teardown()恢复原始全局容器,所有真实服务完好无损
全局容器 (父) 测试子容器
+-----------------------+ +-----------------------+
| HttpClient (真实) | <---- | AuthRepo (Mock) |
| AuthRepo (真实) | | |
| UserRepo (真实) | | |
+-----------------------+ +-----------------------+
resolve(AuthRepo) -> 子容器命中 Mock
resolve(HttpClient) -> 子容器未命中,回退到父容器返回真实服务
快速开始
1. 添加依赖
在功能包的 oh-package.json5 中添加:
{
"dependencies": {
"@core/testing": "file:../../core/testing"
}
}
2. 基础用法
import { describe, beforeEach, afterEach, it, expect } from "@ohos/hypium";
import { TestContainerHelper } from "@core/testing";
import { AUTH_REPOSITORY_KEY, IAuthRepository } from "@shared/contracts";
class MockAuthRepository implements IAuthRepository {
async loginByPassword(params: PasswordLoginRequest): Promise<NetworkResult<Auth>> {
return new NetworkResult<Auth>({ code: 200, data: new Auth() });
}
}
describe("LoginViewModel", () => {
const helper = new TestContainerHelper();
const mockAuth = new MockAuthRepository();
beforeEach(() => {
helper.setup();
helper.registerMock<IAuthRepository>(AUTH_REPOSITORY_KEY, mockAuth);
});
afterEach(() => {
helper.teardown();
});
it("登录成功应更新状态", 0, async () => {
const vm = new LoginViewModel();
await vm.login();
expect(vm.isLoginSuccess).assertTrue();
});
});
API 参考
TestContainerHelper
| 方法 | 说明 |
|---|---|
setup() |
保存当前全局容器,创建子容器并替换全局容器。重复调用未 teardown 时抛出异常 |
registerMock<T>(key, mock) |
注册 Mock 服务到测试子容器。未调用 setup 时抛出异常 |
teardown() |
恢复原始全局容器,清理测试状态 |
参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
key |
ServiceKey |
服务标识符(string 或 symbol),与 @core/di 注册时使用的 key 一致 |
mock |
T |
Mock 服务实例,需实现对应的服务接口 |
使用场景
ViewModel 单元测试
Mock Repository 层,测试 ViewModel 的状态管理逻辑:
describe("UserViewModel", () => {
const helper = new TestContainerHelper();
beforeEach(() => {
helper.setup();
helper.registerMock<IUserRepository>(USER_REPOSITORY_KEY, new MockUserRepository());
});
afterEach(() => {
helper.teardown();
});
it("加载用户列表", 0, async () => {
const vm = new UserViewModel();
await vm.loadUsers();
expect(vm.users.length).assertEqual(3);
});
});
Repository 集成测试
Mock DataSource 层,测试 Repository 的数据转换逻辑:
describe("AuthRepositoryImpl", () => {
const helper = new TestContainerHelper();
beforeEach(() => {
helper.setup();
// Mock HttpClient,避免真实网络请求
helper.registerMock<AxiosHttpClient>(CoreServiceKeys.HttpClient, new MockHttpClient());
});
afterEach(() => {
helper.teardown();
});
it("登录失败应返回错误码", 0, async () => {
const repo = new AuthRepositoryImpl();
const result = await repo.loginByPassword(invalidParams);
expect(result.isSuccess()).assertFalse();
});
});
多个 Mock 服务组合
beforeEach(() => {
helper.setup();
helper.registerMock<IAuthRepository>(AUTH_REPOSITORY_KEY, mockAuth);
helper.registerMock<IUserRepository>(USER_REPOSITORY_KEY, mockUser);
helper.registerMock<AxiosHttpClient>(CoreServiceKeys.HttpClient, mockHttp);
});
最佳实践
1. 始终配对使用 setup/teardown
// beforeEach 中 setup,afterEach 中 teardown,确保每个用例隔离
beforeEach(() => helper.setup());
afterEach(() => helper.teardown());
2. Mock 类实现完整接口
// 推荐:实现接口,编译期检查方法签名
class MockAuthRepository implements IAuthRepository {
async loginByPassword(params: PasswordLoginRequest): Promise<NetworkResult<Auth>> {
return new NetworkResult<Auth>({ code: 200, data: new Auth() });
}
}
// 避免:遗漏接口方法,运行时才暴露问题
3. Mock 数据集中管理
建议在功能包的 src/test/mock/ 目录下统一管理 Mock 类和测试数据:
packages/auth/
└── src/test/
└── mock/
├── MockAuthRepository.ets
└── MockAuthData.ets
注意事项
- 必须先 setup 再 registerMock:未初始化时调用 registerMock 会抛出异常
- 禁止重复 setup:连续调用两次 setup 而未 teardown 会抛出异常,防止丢失原始容器引用
- 子容器不影响父容器:Mock 服务仅存在于子容器中,teardown 后所有真实服务恢复正常
依赖关系
@core/di:依赖注入容器,提供 Container、ServiceKey 等核心类型
文件结构
core/testing/
├── Index.ets # 模块导出
├── oh-package.json5 # 包配置
├── hvigorfile.ts # 构建配置
└── src/main/
├── module.json5 # 模块配置
└── ets/
└── TestContainerHelper.ets # 测试容器辅助类