前言:
我们知道软件通常是在敏捷、竞争激烈的环境中开发的,现在很多时候我们很多开发都是前后端分离的,在前后端完成后就要马上进行联调测试上线等,那么这个时候一款好的mock工具就尤其重要了。
现有方案
1.直接在项目中定义数据
这种就很常见了,在接口完成之后,我们再将数据进行删除重接口中获取数据,这样在联调的时候工作压力都比较大,而且出错率高。
2.通过脚手架的配置文件进行mock
现在很多的公司脚手架其实都有提供mock的文件夹,在里面添加需要mock的接口,匹配到规则的请求就可以进行模拟,这种在联调的时候只需要将mock的数据注释即可,但是在开发的时候很不方便,通过打包工具的配置,大部分是静态加载的,不能支持HMR,每次修改都需要重新启动项目,如果项目庞大的话启动一次就可以摸鱼好久😁。
3.通过抓包工具进行模拟
通过一些流行的抓包工具fiddler进行模拟,对应书写相应的mock规则,这样就可以实现mock,联调的时候也很简单,关闭抓包工具就行了,和项目脱离不存在啥HMR,和代码0耦合可以说是很棒的方法了,就是有一定的上手成本。
4.基于server worker进行mock
利用标准化的 Service Worker API,该 API 旨在拦截网络级别的请求,从而使模拟完全无缝。这不仅保证了使用和不使用模拟时应用程序的行为相同,而且不需要为了模拟而对应用程序代码进行任何更改。观察 DevTools 的“网络”中的请求,可以说是已经忘了这是模拟了。这种可以说是很帅的操作了,对应模拟的数据还可以在开发者工具中进行查看。但是也是有缺点的就是必须要项目中开放一个public文件存放service worker文件,因为创建worker和sw文件需要在同一个源下,这里不进行展开,有兴趣可以学习下service worker的相关内容。这里贴一个已经成熟的方案:msw。
5.浏览器插件mock
通过浏览器插件进行mock,这种mock方法在我看来比前面的几种都是要好的,主要优点有以下几点:。。。。。。。
哈哈哈是下面这里:
- 对于项目代码是0入侵的,这个只有上面的抓包工具可以做到。
- 简单易于操作,我们只需要在插件中输入对应 URL匹配规则即可进行拦截,还有个更棒的功能就是其提供的拦截请求返回功能,要知道我们很多时候是对原有接口的小修小改,那么我们可以直接把拦截拿到该请求的响应内容进行更改可以说是相当方便了。
- 便于团队协作。我们的mock数据要支持导入导出功能,便于团队中协作。这一点他的效果就没有前面在项目中依赖那么好,我们只能导出对应的JSON文件进行分享,而前面的我们可以直接在版本控制器中一起流转。
官网对插件的使用也是介绍的很明白,感兴趣的自己学习使用:tweak,但是它的有些功能是收费的,不保证以后基础功能也收费,而且在拦截请求的时候有些卡顿。
正文开始
讲了这么多种方案你不会以为我要进行总结了吧?我们前面说了很多种方案,其中我觉得最好的就是最后一种,但是它没有开放的源码,且收费(这很难受),作为一个切图仔,我觉得这样的功能实现起来应该也是不难的,于是动手实现了一个简易版的mock插件,由于日常中我们最经常使用的基本都是XHR,所以我连fetch的都没做哈哈,后续有时间再加了,最主要就是实现两个功能:1.获取原有请求的响应体可以进行更改;2.可以控制请求的响应内容;我们先看下效果:
可以看到一开始的时候我们的请求返回的是一个html这其实是脚手架对资源请求找不到的默认请求,我们通过点亮绿色图标拦截到了刚才的两个请求内容,由于不是可解析的JSON数据,所以填写上默认值,否则就是原有内容(注意我这里的编辑JSON比5的好用,他的不会错误提示哈哈)。而后我们开启mock按钮再次请求的时候我们的数据就变成我们的mock数据了,是不是很帅。
简单的讲下实现思路
首先对于请求的响应浏览器插件是不允许我们进行获取的(虽然其有提供请求完成的事件监听,但是你拿不到对应的响应内容,这样就不能做响应内容的回显了)。这里我原来还想通过service worker来获取响应内容以及改变响应内容,我们知道这对于service worker是非常简单的,我们只需要监听fetch事件,但是我试了很多方法都被其同源策略限制了,要是有大佬知道怎么操作,评论区求教下。最后是通过注入js脚本,修改XHR对象实现的,我们只需要修改XHR对象的方法,在请求成功的时候获取对应的响应内容,发回我们的浏览器popup中,我们就能做到回显响应内容,再在修改对应的需要mock的请求响应内容就能实现我们的mock操作,讲的很抽象,我们直接看代码:
1.获取响应内容:
(function (xhr) {
var XHR = XMLHttpRequest.prototype;
var open = XHR.open;
var send = XHR.send;
XHR.open = function (method, url) {
this._method = method;
this._url = url;
return open.apply(this, arguments);
};
const filterUrl = ['xxx.com'];
XHR.send = function (postData) {
this.addEventListener('loadend', function (e) {
const data = e?.target?.response;
if(!filterUrl.some(item=>this._url.includes(item)))
{
window.postMessage({label: this._url, value: data}, '*');
}
});
return send.apply(this, arguments);
};
})(XMLHttpRequest);
我们只要在请求loadend的时候把对应的响应内容进行回传即可。
2.mock数据
(function startMockOnreadystatechange() {
const originOpen = XMLHttpRequest.prototype.open;
const setWriteKeys = ['status', 'statusText', 'response', 'responseText', 'readyState','onload'];
XMLHttpRequest.prototype.open = function (_, url) {
const localDataStr = localStorage.getItem('cai_mock_data');
const localData = JSON.parse(localDataStr);
originOpen.apply(this, arguments);
const find = localData?.find(item => url.includes(item.url));
if (find&&find.startMock) {
setWriteKeys.forEach(key => {
const props = Object.getOwnPropertyDescriptor(this, key);
Object.defineProperty(this, key, {
...props,
writable: true,
readable: true,
});
});
setTimeout(() => {
this.status = 200;
this.readyState = 4;
this.statusText = 'OK';
this.response = find.json;
this.responseText = find.json;
this.dispatchEvent(new Event('readystatechange'));
});
}
};
})(XMLHttpRequest);
对应的修改请求中的响应数据,这样响应数据就按照我们的设置生效了。
最后mock方案千千万,适合自己的才是最好的,没有适合的就自己造一个吧。