目录
- 为什么需要 mock
- mock 数据的方式
- json schema
- 可视化的 mock
- 在 webpack 实现 mock server 需要的知识
- 实现 webpack mock server
- 小试牛刀
- 大刀阔斧
- 后话
为什么需要 mock
至于平时开发为什么需要 mock 数据,应该大多数的同学都非常清楚了;如果前后端同步开发的话,少不了这一步,在需求评审,技术评估等流程通过后,前后端就会约定接口 api 的字段(但是在部分公司可能会少了这一步),确认接口 api 字段约定之后,前端就可以通过 mock server 去 mock 数据进行开发了,不需要等后端开发完 api 接口再去对接,但是有些同学在开发的过程中经常已经把 ui 弄好了,就在苦苦等后端大哥的接口...白白浪费了不必要的时间,如果可以自己 mock 数据开发,那等后端接口都好了只需要把域名或者接口前缀换一下再联调一下就万事大吉了。
mock 数据的方式
json schema
有些同学喜欢在代码里面用 json schema 的形式去 mock 数据,比如:
export function xhr(params = {}) { | |
if (process.env.NODE_ENV === "development" && useMock) { | |
return delay().then(() => Promise.resolve(require("./mock/list.json"))); | |
} | |
return request({ | |
url: "xxxxx", | |
method: "POST", | |
}); | |
} | |
function delay(ms =) { | |
return new Promise((resolve) => { | |
setTimeout(() => { | |
resolve(); | |
}, ms); | |
}); | |
} |
优点:简单。
缺点:第一,需要侵入逻辑代码里面;第二,没办法真正的模拟 ajax 请求,因为这种 mock 是不会发起 http 请求的;第三,不能获取传递的参数去做对应的事情;第四,不能修改 http 的状态;第五,打包项目的时候还会被打包进 bundle。
结论:缺点明显大于优点。
可视化的 mock
市面上有很多可视化的 mock api 方案,比如 apiFox。
优点:ApiFox 集成了 Postman + Swagger + Mock + JMeter,是一款做得比较的好的可视化 mock 解决方案。
缺点:如果只有前端团队在单独使用,就有点大材小用没必要,如果是前后端测试都同时在使用的话,那就是一个不错的选择。
结论:ApiFox 更像一个团队协作的 mock 工具。
在 webpack 实现 mock server 需要的知识
如果要自己在 webpack 项目的搭建一个定制化的 mock server 需要如下的知识点。
- 一点点的 webpack 知识
- 一点点的 node 知识
很简单的啦!
实现 webpack mock server
小试牛刀
在 webpack 中实现定制化的 mock server ,需要借助 webpack-dev-server,也就是 webpack 配置下 devServer 字段。该字段下提供了一个onBeforeSetupMiddleware的一个钩子,回调参数里面为我们提供了一个app参数,参数是一个 node 的服务。
既然知道了app是一个 node 服务,那让我们小试牛刀一下(很快不疼,一下就过去了 🐶)。
// webpack.config.js | |
module.exports = { | |
devServer: { | |
onBeforeSetupMiddleware:(server){ | |
server.app.get('/api/list',(req,res,next){ | |
res.json({ | |
code:, | |
data:{ | |
name:"孤猎" | |
} | |
}) | |
}) | |
} | |
}, | |
}; | |
// 随便一个js定义一个ajax请求 | |
let getData = ()=>{ | |
fetch('/api/list').then(res=>res.json()).then(res=>{ | |
console.log(res.data) | |
}) | |
} |
这里就实现了一个简单的 mock server 的了,是不是很简单。
但是这是有缺点的,总不能没定义一个 api 都在 onBeforeSetupMiddleware 的 server.app.xxx 吧,这得多麻烦,让我们稍微修改一下代码,大概用个小 50 行代码就好。
大刀阔斧
确认需要实现一下怎么的 mock server:
- 只读取跟目录的 mock 文件夹下的 js 文件
- js 文件 mock 数据需要通过module.exports={}导出
- mock server 可以修改各种状态,支持 GET,POST 等请求
- 支持延时
- 期望的使用方式:
module.exports = {"GET /api/list":(req,res)=>{},"POST /api/list":{},"GET /api/list 3000":(req,res)=>{} },
- 可以是函数也可以是 json,3000 是延时时间,请求 api 分三段请求方法 路径 延时。
首先,先把刚才的 get 请求改成 use,让所以的请求都打进来,打进来后不管三七二十一先调 next;然后加个判断,只处理包含 api/mock 的请求,其他的让它改干嘛就干嘛。
具体的实现就直接贴代码了,具体的看注释就好,在注释里详细解释。
具体实现就抽离到一个单独的 js 文件实现,具体哪个文件看个人喜欢了。
const fs = require("fs"); | |
const path = require("path"); | |
module.exports = function () { | |
// 这里收mock数据的根目录,我们只认这目录下文件 | |
let mockDataPath = path.resolve(__dirname, "../mock/"); | |
// 判断根目录是否存在mock目录 | |
let existsMockDir = fs.existsSync(mockDataPath); | |
// 获取mock目录下的所有文件的mock数据 | |
let getMockData = () => { | |
// 如果mock目录存在就走if逻辑 | |
if (existsMockDir) { | |
/** | |
* 通过readdirSync获取mock目录下的所有文件名称 | |
* 再通过require取出数据 | |
*/ | |
let modules = fs.readdirSync(mockDataPath); | |
return modules.reduce((pre, module) => { | |
return { | |
...pre, | |
...require(path.join(mockDataPath, "./" + module)), | |
}; | |
}, {}); | |
} else { | |
console.log("根目录不存在mock文件夹,请创建一个根目录创建一个mock文件夹"); | |
return {}; | |
} | |
}; | |
// 该函数负责重新处理请求的路径 | |
let splitApiPath = (mockData) => { | |
let data = {}; | |
for (let path in mockData) { | |
let [method, apiPath, sleep] = path.split(" "); | |
let newApiPath = method.toLocaleUpperCase() + apiPath; | |
data[newApiPath] = { | |
path: newApiPath, | |
method, | |
sleep, | |
callback: mockData[path], | |
}; | |
} | |
return data; | |
}; | |
// 该函数是一个延时函数 | |
let delayFn = (sleep) => { | |
return new Promise((resolve) => { | |
setTimeout(() => { | |
resolve(); | |
}, sleep); | |
}); | |
}; | |
// 最后返回一个函数 | |
return async (req, res, next) => { | |
let { baseUrl, method } = req; | |
// 只处理请求路径包含api的请求 | |
if (baseUrl.indexOf("api") === - || !existsMockDir) { | |
return next(); | |
} | |
let mockData = splitApiPath(getMockData()); | |
let path = method.toLocaleUpperCase() + baseUrl; | |
let { sleep, callback } = mockData[path]; | |
let isFuntion = callback.__proto__ === Function.prototype; | |
// 如果mock api 有延时存在 | |
if (sleep && sleep >) { | |
await delayFn(sleep); | |
} | |
// 如果mock api 的值是一个函数 | |
if (isFuntion) { | |
callback(req, res); | |
} else { | |
// 如果mock api 的值是一个json | |
res.json({ | |
...callback, | |
}); | |
} | |
next(); | |
}; | |
}; |
在 webpack.config.js 引入刚刚实现的方法
module.exports = { | |
devServer: { | |
onBeforeSetupMiddleware:(server){ | |
server.app.use('*',mockServer()) | |
} | |
}, | |
}; |
使用
在项目根目录定义一个 mock 目录,随便创一个 js 文件。
module.exports = { | |
"GET /api/list": { | |
code:, | |
data: { | |
list: [ | |
{ | |
name: "syf", | |
age:, | |
}, | |
], | |
}, | |
}, | |
"POST /api/list": (req, res) => { | |
res.json({ | |
code:, | |
data: { | |
list: [ | |
{ | |
name: "gulie", | |
age:, | |
}, | |
], | |
}, | |
}); | |
}, | |
}; |
到此为止,期望的定制化的 mock serve 就大功告成了。
后话
如果各位同学还有更高的追求的话,可以在此基础上继续定制化自己的需求,还有在根目录的 mock 所有数据不会被打包进 bundle,也没有侵入到逻辑代码里面。
如果各位同学对 vite 项目怎么实现 mock server 感兴趣的话,可以留言或者私信,需求多的话,会出一篇在 vite 项目的 mock server。