深圳幻海软件技术有限公司 欢迎您!

多状态页面中的 Mock 方案

2023-02-28

我们有时候会遇到一个业务页面存在很多个状态,甚至子状态,比如订单详情就是其中的典型,涉及从订单创建到订单结束,以及售后等流程。维护起来每个状态对应一份数据,虽然我们QA提供了数据构造平台,但构造一份对应状态的数据还是需要花费不少时间,而且串行流程一旦出错的话只能重新来一遍。 后期维护阶段也

我们有时候会遇到一个业务页面存在很多个状态,甚至子状态,比如订单详情就是其中的典型,涉及从订单创建到订单结束,以及售后等流程。维护起来每个状态对应一份数据,虽然我们 QA 提供了数据构造平台,但构造一份对应状态的数据还是需要花费不少时间,而且串行流程一旦出错的话只能重新来一遍。 

后期维护阶段也不容易构造对应状态的数据,导致排查页面问题比较耗时。

另外一个问题就是从头熟悉业务的话成本比较高,如果有一个直观的页面能够看到页面样式会好很多。

以上就是设计一个多状态 mock 工具初衷,让开发者在页面中直接选择对应状态,就可以切换到对应页面。

技术选型

目前转转 app 测试包,webview 页面气泡浮层已经有两个了——客户端工具和 eruda,再多就乱套了,所以最好集成在现有的工具基础上。客户端部分功能我们接触不到也不了解,在现有条件下只有 eruda 可用。

Eruda 是一个很强大的前端页面调试工具库,我们客户端 webview 也内置了,在测试包中可以很方便的借助 eruda 调试页面,观察日志。同时 eruda 也支持插件,通过插件来扩展 eruda 的功能。我们的工具就基于 eruda 插件来实现。

实现效果预览:

插件预览图

总体流程如下:

插件流程

整个流程大概三个部分组成

  • 业务逻辑改造
  • Eruda 插件
  • Mock 数据整理

业务逻辑改造

首先要实现这样一个方案核心依赖于业务使用的请求库,以及是否能够对请求库进行修改。

我们业务使用的基于 axios 的请求库,其暴露了实际发送请求的 adapter 逻辑,我们可以基于 adapter 来实现接口方法的拦截。

axios adapter

借助 axios-mock-adapter[1],可以很方便的实现我们的需求。

import { axiosInstance } from '@zz/fetch';
import MockAdapter from 'axios-mock-adapter';

export const mock = new MockAdapter(axiosInstance)

try {
  // 借助 localstorage 实现 eruda 插件和 axios-mock-adapter 通信
  const mockReqConf = JSON.parse(localStorage.getItem('_mock_req'))
  if (mockReqConf && mockReqConf.mockId) {
    mock.onGet(api.getOrderDetail).reply(config {
      // console.log('mock api', api.getOrderDetail)
      return axiosInstance.get('https://mockrepository.zhuanzhuan.com/orderdetail?mockId=' + mockReqConf.mockId)
    })
  }

  mock.onAny()
    .passThrough()

  const isProduction = process.env.NODE_ENV === 'production'
  if (isProduction || (mockReqConf && mockReqConf.mockId === '')) {
    // sessionStorage 实现开关,如果没有此配置,就重置设置的 mock 拦截
    if (!sessionStorage.getItem('mock-adapter')) {
      mock.restore()
    }
  }
} catch (error) {
  console.log('mock adapter config error', error)
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.

Eruda 插件

插件的目的是实现两个功能,一个是总开关,另一个是 mock 数据的展示和切换。

总开关借助 sessionStorage,webview 关闭 sessionStorage 数据清除,这样避免了一进入页面就是 mock 数据,防止忘记关闭以及频繁操作,需要的时候才打开。

Mock 数据的展示配置在一个配置文件中,公司有统一的配置中心,基于携程 Apollo 实现。这个文件的作用是映射对应的状态和 mock 数据来源,来源是统一的,所以只用参数区分即可。

示例如下:

[
  {
    "id": "/order/detail",
    "list": [
      {
        "title": "正向流程",
        "list": [
          {
            "title": "下单待发货",
            "id": "1-0-1"
          },
          {
            "title": "寄卖下单",
            "id": "1-1-0"
          },
          {
            "title": "发货运输中",
            "id": "1-2-0"
          },
          {
            "title": "平台质检",
            "id": "1-3-0"
          }
        ]
      },
      {
        "title": "逆向流程",
        "list": [
          {
            "title": "申请退回-退回中",
            "id": "2-3-2"
          }
        ]
      }
    ]
  }
]
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.

​id​​ 用于标识当前页面链接,是否有配置 mock 数据,所以也支持配置多个页面。

Eruda 插件的实现参考 eruda 官方文档。

  • ​Eruda 如何写插件[2] https://github.com/liriliri/eruda/blob/master/doc/PLUGIN.md
  • Eruda 工具库[3] https://licia.liriliri.io/docs_cn.html

Eruda 插件写法类比较像一个小的模板库,eruda 提供了插件模板,绑定事件方式类似于 jQuery 语法,eruda 提供的 licia 工具库均有对应方法,参考即可。

Eruda 插件示例代码:

(function(root, factory) {
    if (typeof define === 'function' && define.amd) {
        define([], factory);
    } else if (typeof module === 'object' && module.exports) {
        module.exports = factory();
    } else {
        root.erudaPlugin = factory();
    }
})(this, function() {
    return function(eruda) {
        var Tool = eruda.Tool,
            util = eruda.util;

        var Plugin = Tool.extend({
            name: 'plugin',
            init: function($el) {
                this.callSuper(Tool, 'init', arguments);
                this._style = util.evalCss(
                    [
                        '.eruda-dev-tools .eruda-tools .eruda-plugin {padding: 10px;}',
                        '.eruda-tip {padding: 10px; background: #fff; color: #263238;}'
                    ].join('.eruda-dev-tools .eruda-tools .eruda-plugin ')
                );
                $el.html(
                    '<div class="eruda-tip">Put whatever you want here:)</div>'
                );
            },
            show: function() {
                this.callSuper(Tool, 'show', arguments);
            },
            hide: function() {
                this.callSuper(Tool, 'hide', arguments);
            },
            destroy: function() {
                this.callSuper(Tool, 'destroy', arguments);
                util.evalCss.remove(this._style);
            }
        });

        return new Plugin();
    };
});
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.

Mock 数据整理

这一部分其实是比较耗费体力的工作。需要整理每一个状态的数据,存储起来。前面提到公司提供的一个接口平台,基于去哪儿 YAPI 搭建,其提供了每个接口 mock 能力,我们添加 mock 数据即可。前面配置文件中每一个状态下的 id 其实就是 mock 数据的参数。

使用的时候,点选每一个状态,将状态值写入 localStorage 当中,刷新页面。axios 请求时检测是否有 localStorage 配置,有的话取出对应值,拼接到 YAPI mock 接口请求当中,获取到 mock 数据,然后页面就是 mock 数据渲染出来的了。

初步尝试后,可以让我们在页面开发过程中可以很方便得查看不同页面下的页面展现,相比于之前的要么是查询线上数据,要么是通过数据构造一步一步找到对应状态,都大大节省了时间,提升了效率。

同时对于产品和设计同学,也可以快速的找对对应页面的样子,方便产品对现有页面进行调整,设计同学对页面还原度进行检查等等。

不足之处也有,就是数据的维护,现在每一个状态下存储的都是一份完整的数据,如果某一部分调整了的话,那么所有的数据都要修改,数据越多,维护成本相对来说也越高。

以上是基于现有基础能力搭建出来的一个简单工具,当然还有更多改进空间,请多指教。

参考资料

​[1]axios-mock-adapter: https://github.com/ctimmerm/axios-mock-adapter

[2]Eruda 如何写插件: https://github.com/liriliri/eruda/blob/master/doc/PLUGIN.md

[3]Eruda 工具库: https://licia.liriliri.io/docs_cn.html