Mock自定义配置

为什么需要 Mock

images/20190930145233.jpg

这样的场景,相信大家会觉得似曾相识。

为此,我们就需要使用一些工具来帮助我们将业务单元之间尽量解耦,它就是Mock

实现 Mock

古代

没有出现 Mock 前,为了能模拟数据,一般屏蔽请求代码,然后写死数据,比如:

1
2
3
4
5
6
7
8
9
10
11

// this.$http.get("/maps/aoi_user_search/").then(
//   function(res) {
//     setData(res);
//   },
//   function(res) {
//     console.log(res.status);
//   }
// );
let res={a:1,b:2}
setData(res);

这种方式很简单,起码当初就照这个方式继续开发了,虽然后续接口有数据,改起来很麻烦。但是也会常常遇到忘记把模拟数据移除,导致实际使用的时候一直是假数据,而非真实数据,为此出现过多次。所以为了解决这个问题,就用到了 Mock。

近代

在这个时候,我们就拥有了 MockJS,通过使用 MockJS 我们能根据模板和规则生成复杂的接口数据,而无需我们自己动手去书写,例如:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//apis
import api from '../api/index';
import Mock from 'mockjs';
var obj = {'aa':'11', 'bb':'22', 'cc':'33', 'dd':'44'};
function getApiMessage({
    return new Promise((resolve) => {
        resolve(Mock.mock('http://test.com', {
"user|1-3": [{ // 随机生成1到3个数组元素
'name': '@cname', // 中文名称
'id|+1': 88, // 属性值自动加 1,初始值为88
'age|18-28': 0, // 18至28以内随机整数, 0只是用来确定类型
'birthday': '@date("yyyy-MM-dd")', // 日期
'city': '@city(true)', // 中国城市
'color': '@color', // 16进制颜色
'isMale|1': true, // 布尔值
'isFat|1-2': true, // true的概率是1/3
'fromObj|2': obj, // 从obj对象中随机获取2个属性
'fromObj2|1-3': obj, // 从obj对象中随机获取1至3个属性
'brother|1': ['jack', 'jim'], // 随机选取 1 个元素
'sister|+1': ['jack', 'jim', 'lily'], // array中顺序选取元素作为结果
'friends|2': ['jack', 'jim'] // 重复2次属性值生成一个新数组
},{
'gf': '@cname'
}]
}));
    })
    // return api.getApiMessage();
}
/**
*通过模板 生成的数据格式
*{
* "user": [
* {
* "name": "董静",
* "id": 88,
* "age": 25,
* "birthday": "2015-04-01",
* "city": "湖南省 怀化市",
* "color": "#c0f279",
* "isMale": false,
* "isFat": false,
* "fromObj": {
* "dd": "44",
* "aa": "11"
* },
* "fromObj2": {
* "bb": "22",
* "cc": "33"
* },
* "brother": "jack",
* "sister": "jack",
* "friends": [
* "jack",
* "jim",
* "jack",
* "jim"
* ]
* },
* {
* "gf": "田杰"
* }
* ]
*}
*
*/

这种方式也只是方便我们造假数据,而并不能方便的抽离出我们的代码,而且数据也不足够真实。
有关 mockjs 相关语法参考http://mockjs.com/examples.html#String
这个时候想一下,我们 Mock 数据的需求:

  • 模拟数据
  • 模拟数据与代码完全分离
  • 通过一些配置,可以只获取部分 Mock 数据,最好足够真实

首先,如果我们想要把代码跟数据完全分离,我们必须想办法在请求的时候做一些操作,让本应请求正式数据的接口去请求 Mock 数据,做一个请求拦截,请求拦截的方式有两种:

  • 一种是修改请求的链接,来达到 Mock 数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//main.vue
service.getBuildingListByPage({
...params,
mock:true
})
//server.js
getBuildingListByPage: params => {
    let baseApi=env.baseApi
    if(params.mock){
      baseApi='127.0.0.1:8080'
    }
    return fly.get(
      env.baseApi + "g/bam/c/quote/building-list",
      params
    )
  }

此方法需自己搭一个 node 服务,然后写一些接口返回数据。

  • 另外一种就是检测出 Mock,直接从 mock 文件中取出数据
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
//main.vue
service.getBuildingListByPage({
...params,
mock:true
})
//mock/db.js
export default {
getBuildingListByPage: {
err_code: 0,
err_msg: "ok",
data: [
{
time: "2019-05-29 00:00:00",
aging: 0
},
{
time: "2019-05-29 01:00:00",
aging: 16
}
]
}
}
//server.js
import mockData from 'mock/db.js';
getBuildingListByPage: params => {
    let baseApi=env.baseApi
    if(params.mock){
      return mockData[getBuildingListByPage]
    }
    return fly.get(
      env.baseApi + "g/building-list",
      params
    )
  }

乍一看好像第二种方式似乎更简单,事实也确实如此。但是这样用起来还不是很方便,而且接口多了,Mock 数据文件会很大,需要做一个拆分。所以就有了后来的配置。

现代

tips:现在的配置基于webpackdevServer
在 webpack 中做转发代理,所有请求会先过 before 这个回调函数:

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
const path = require('path');

const webpack = require('webpack');

const merge = require('webpack-merge');

const BaseConfig = require('./webpack.base.js');

const apiMocker = require('mocker-api');
//webpack配置
mode: 'development',
  devServer: {
    host: '0.0.0.0',
    port: 9001,
    headers: {
      'Access-Control-Allow-Origin''*'
    },
    historyApiFallback: {
      rewrites: [{
        from/.*/g,
        to: '/www/view/index.html'
      }]
    },
    proxy: {
      '/api': {
        target: 'http://10.12.67.192:8091/'
      }
    },
    before(app) {
//https://www.webpackjs.com/configuration/dev-server/#devserver-before
      apiMocker(app, path.resolve(__dirname, '../mock/index.js'));
    }
  },

Mock 资源文件
ce8f8163afd3df184358e55c9ef5011e.png
JSON 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
  "err_code"0,
  "err_msg""ok",
  "mock"true,
  "data": {
    "poi": {
      "z_id""111111111111",
      "name""中国技术交易大厦大厦大厦",
      "province""",
      "city""北京市",
      "district""海淀区",
      "longitude"116.307499005,
      "latitude"39.111,
      "addr""",
      "category""房产小区:商务楼宇",
      "category_code"281200,
      "expiration_label"0,
      "phone"""
    }
  }
}

Mockjs 配置

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
const delay = require("mocker-api/utils/delay");
const fs = require("fs");
const dirCur = fs.readdirSync(__dirname);
const path = require("path");
const MOCK = process.env.MOCK || process.env.mock || process.env.Mock;
const apiList = {};
dirCur.forEach((item) => {
const basename = path.basename(item, ".json");
if (basename !== "index.js") {
const apiName =
"POST /api/" +
basename.replace(/[A-Z]/g, (math) => {
return "/" + math.toLowerCase();
});
let apiData = fs.readFileSync(path.join(__dirname, item), "utf8");
try {
apiData = JSON.parse(apiData);
} catch (e) {
console.log(item, e);
}
if (MOCK || apiData.mock) {
apiList[apiName] = (req, res, next) => {
console.log("Mock API:" + req.path);
res.json(apiData);
};
}
}
});
// 'GET /api/edit/apply': (req, res) => {
// const { owner, repo, ref } = req.params;
// return res.json({
// id: 1,
// owner, repo, ref,
// path: req.params[0]
// });
// },

// const proxy = loadData({
// 'POST /api/edit/apply': './editApply',
// 'POST /api/verify/apply': './verifyApply',
// 'POST /api/accept/apply': './editApply',
// 'POST /api/edit/submit': './editSubmit',
// 'POST /api/verify/submit': './editSubmit',
// 'POST /api/accept/submit': './editSubmit',
// 'POST /api/rawore/get': './raworeGet',
// 'POST /api/poi/history': './poiHistory',
// 'POST /api/edit/history': './editHistory',
// 'POST /api/verify/history': './editHistory',
// 'POST /api/accept/history': './editHistory',
// 'POST /api/user/get': './userGet',
// 'POST /api/work/history': './historyList',
// 'POST /api/statistic/gainproduct': './gainproduction',
// 'POST /api/statistic/accuracy': './accuracy',
// 'POST /api/statistic/aging': './aging',
// 'POST /api/company/list': './companyList',
// 'POST /api/user/list': './listuser'
// });
module.exports = delay(apiList, 10);

json 文件采取驼峰命名方式,然后通过正则判断大写,替换成/+小写路径,然后用 node 中的 fs 读取当前文件夹下所有文件,得到文件列表遍历,读取文件内容,最后判断是否开发模式或是否开启 Mock,然后返回数据。

关于 MOCK 全部开启,package.json配置:
1641651616

mocker-api-json 最近刚刚发布的 npm 包

mocker-api-json

参考资料

https://www.jianshu.com/p/adb6ff1df3d6
https://www.npmjs.com/package/mocker-api