前言
本文提供一种实现Mock数据的解决方案,解决思路为核心,实现则以OkHttp3为例,可根据提供的思路将方案嵌入实际项目代码中。
为何要Mock
开发项目中,不管是等待API的完成,还是测试API正常、异常流是否达到预期,都需要相应的响应数据验证。但是等待他人提供API是痛苦的,Mock数据也痛苦的。
常见解决方案
达到目标可以有很多种路径,Mock方案也有很多种。常见的Mock方案有:
硬编码
比如有一个API需要测试某JSON数据,则将此String在回调处进行替换以达到目的。优点为操作方便,缺点如:
- 调整需要重新编译
- String过长可能无法完全复制
- 如有多个API需要进行Mock,杂乱无章
工具映射
借助如Charles等工具可以将API等信息进行替换,优点为覆盖面广,可实时Mock。缺点为:
- 有操作成本
- 需要记住哪些API进行了Mock并在不需要时关闭功能
本地仓库
本地存有Mock数据文件,可网络层等合适位置对API进行查阅操作,命中时替换成Mock数据。优点为易维护,全面。缺点为:
- 调整困难,需要重新打包
- 可通过adb shell等方式动态替换数据文件,但操作有成本
放个大招
理想状态下,Mock应该满足以下要求:
- 不需要对已有程序进行改动,因为需要进行回退
- 易于管理API,Mock哪些API一目了然,可配置
- 实时操作,并且操作成本低
综合以上需要,给出方案思路大致如图
Mock方案.png
图中表达如下
- 方案嵌入位置选在网络层中的拦截层,得益于拦截层得天独厚的位置,在拦截层中实现Mock方案最方便,也最透明。了解拦截层点我。本篇文章的实现也是基于OkHttp3的拦截链来进行的,当然,在实际项目中拥有自主网络框架的拦截层再好不过。如果没有拦截层,也可以参考这个思路,实现在合适的位置。因为网络请求一定会有某个公用的入口和出口,在此类位置进行即可。
- 需要理解的一点是,Mock不代表没有网络请求,对于Mock来说,最关心的是拿到Mock数据,从何而来倒不是核心。
- 本地会有一份配置表,配置表记录的哪些API需要进行Mock和Mock地址。这份配置表不需要使用文件存储,实现某个共享对象如单例存储即可。而配置表的获取可以选择合适的时机从远端同步,比如程序启动时、发生网络请求时做预检查。
- 网络请求会被拦截层间层,当API在配置表中命中,则这证明此API需要Mock,将实际请求重定向为配置表配置的Mock地址从远端拿到Mock数据即可,反之,API未命中则维持原先操作。
- 各个Mock地址则保存的合适数据,保证实时性。
如此实现的原因则是综合了之前常见解决方案的优缺点:
- 进行Mock不需要修改程序代码
- 易于操作,需要进行Mock的API在远端配置即可
- 配置更变即配置表有改动时,不需要重新打包,重启应用即可
- 有实时性,在不需要进行Mock时,Debug环境下仅更改配置表即可
- 更新Mock的API数据时,重新触发请求即可
如上所诉,需要一个远端仓库存储Mock数据和配置表,如果没有后端条件或者没有合适方案,可以使用Easy-Mock 点我,文章会基于此进行实现,如何使用Easy-Mock参考官方文档即可。
实现
首先说明,实现部分,代码不多,仅摘取了核心代码进行解说,因为涉及到需要依赖具体环境进行的代码,则以一个函数进行表达,代码中会做详细注释。
假设已拥有远端仓库,则配置表是这样子的
{
"open": "true",
"reflects": [{
"url": "www.baidu.com",
"mockKey": "baidu"
},
{
"url": "text",
"mockKey": "test"
}
]
}
open字段代表Mock方案是否启用,reflects是映射数据,其中url代表需要进行mock的API,mockKey则代表进行拼接的key。以Easy-Mock来说,会提供基本url并通过不同的key来访问具体的Mock数据,假设基本url为“www.test.com”,则当mockKey为test时,mock地址为“www.test.com/test”。
而mock地址返回的数据,则视具体情况而定,比如下面一个简单的JSON。
{
"code": 200,
"message": "success",
"entry": "your data"
}
如果是使用了Easy-Mock的话,可以类似这样的仓库图。
easy-mock api列表.jpg
仓库准备完毕,以下为核心代码
public class OKMockInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
// 获取原请求
Request request = chain.request();
// mock 条件,是具体情况而定
if (isMock()){
// 存储且管理远端配置表的数据,url检测也是通过此类检查
MockWarehouse mockWarehouse = MockWarehouse.instance();
/**
* 拉取配置表
*/
if (mockWarehouse.isInit()){
// 配置表地址
String mockConfigUrl = mockWarehouse.getConfigUrl();
// 拉取配置表的请求
Request configRequest = new Request.Builder()
.url(mockConfigUrl)
.build();
// 拿到配置表信息
Response configResponse = chain.proceed(configRequest);
// 检查Response状态以及Response结果
if (configResponse.code() == 200){
// 这里拿到response的string,
// 在okHttp3中,使用peekBody()而不是body()获取ResponseBody,因为后者会导致
// 流关闭,框架校验不通过。
String configInfo = configResponse.peekBody(Integer.MAX_VALUE).string();
Log.d("mMock", "mockUrl : " + configInfo);
// 这一步校验是希望配置表满足某些格式,视具体情况
if (configInfo.contains(MockWarehouse.IDENTIFY)){
// 更新MockWarehouse
mockWarehouse.updateMockReflect(configResponse.peekBody(Integer.MAX_VALUE).string());
}
} else {
// 拉不到配置,直接做原先的请求
return chain.proceed(request);
}
}
/**
* 尝试获取mock 的url
*/
String mockUrl = mockWarehouse.getMockUrl(request.url().toString());
if (!TextUtils.isEmpty(mockUrl)){
Log.d("mMock", "mockUrl : " + mockUrl);
// 构造远端mock的请求
Request mockRequest = new Request.Builder()
.url(mockUrl)
.build();
//拿到远端mock请求的结果
Response mockResponse = chain.proceed(mockRequest);
// 远端mock状态
if (mockResponse.code() == 200){
String responseString = mockResponse.peekBody(Integer.MAX_VALUE).string();
// 校验response
if (!TextUtils.isEmpty(responseString)){
Log.d("mMock", "response : " + responseString);
// mock数据成功, 返回mock的response
return mockResponse;
}
}
}
}
// 无需mock或者mock不成功,返回原来的请求
return chain.proceed(request);
}
/**
* 进行mock的环境条件,视具体情况而定
* @return
*/
private boolean isMock(){
return true;
}
}
实现中需要注意几点:
- isMock()视具体情况而定,比如Release包不需要进行mock
- 可见在此拦截器中,会根据情况的不同进行了若干次请求,对于上层来说,关心的依旧是Reponse,从何而来是透明的
- 需要类似MockWarehouse存储远端配置表信息并负责做URL校验
- MockWarehouse的配置表信息同步时机需要进行考虑,视具体情况而定是否需要考虑并发问题
- 可以的话,希望Reponse能保持格式化
总结
以上完成了Mock方案,其中思路可根据实际需要进行实现,核心为:
- Mock也是有网络交互的过程
- 在请求的统一入口有一层控制,视具体情况重定向
- 远端的配置表决定API的Mock与否,本地需要同步此信息并据此进行后续操作
网友评论