写在前面
通过阅读本文,你将对Mock的使用有一定的了解,对前后端分离的概念有了更深一步的认识,对Koa的使用有一定的了解。本文先从背景出发去抛出“我们为什么要用Mock?”的灵魂拷问,紧接着我们通过Mock在前后端的使用来进行实战落地,最后我们再总结回顾,展望高配版的Mock Server。
本文不会像念经一样把官方文档的API抄一遍告诉读者这个怎么用,那个怎么用,更多地是提供一个思路或者想法以及项目的落地带着大家学习Mock的使用。因为我坚信“官方文档始终是最权威的文档。”,所以“Do Not Repeat"原则,不要做念经这种事情,因为官方文档写的已经很详细了。还有就是有些项目API茫茫多,如果都全面地写一遍,累死啦,这个是官方文档干的事情。技术只有转换成生产力,才是程序员的赚钱的核心竞争力,劝君莫惜金缕衣,劝君惜取少年时。
这里简单地罗列下Mock的整体知识,读者后续可以根据这个大纲有选择地去看。
API
- Mock.mock()
- Mock.setup()
- Mock.random()
- Mock.valid()
- Mock.toJSONSchema()
据不完全统计,Mock支持18种数据定义,172种数据定义写法。具体的参见:http://mockjs.com/examples.html
使用Mock的背景
我们为什么要使用Mock?
传统的非前后端分离的项目,后端老哥除了要做对接服务器数据库相关的工作,还要搞前端页面,太多太累太杂了。随着时代的发展、人类社会的进步,编程技术的更新迭代,慢慢地开始有了专职的前端程序员和后端程序员等等,项目越来越复杂,前后端的要求度逐步提高,尤其是Node.JS技术的迅猛发展,十一年弹指一挥间,在npm、github各类库和项目如雨后春笋般蹭蹭蹭地雄起,给开发者提供了很多解决方案,这也使得前后端分离成为可能。事物存在的即是合理的,但我们也要辩证地去看待这个事物。前后端分离项目的落地比前后端不分离的落地增加了开发人员对接沟通的成本,在某些场景下,前端开发会受限制于后端开发,接地气地说就是后端接口没写好没提供前端可能就无从下手了,为了解决这个问题,我们需要进行相关地Mock,来模拟后端返回的数据也好或者后端的接口也好,总之,我们需要一个Mock Server。
Mock在前端的使用
安装
# npm安装
npm i mockjs -D
引入方式
传统script脚本引入
去Bootcdn引入相关的脚本,地址:https://www.bootcdn.cn/Mock.js/, 形如
<!-- 生产环境 -->
<script src="https://cdn.bootcdn.net/ajax/libs/Mock.js/0.1.1/mock-min.js"></script>
<!-- 开发环境 -->
<script src="https://cdn.bootcdn.net/ajax/libs/Mock.js/0.1.1/mock.js"></script>
ES Module
import Mock from 'mockjs';
CommonJS
const Mock = require('mockjs');
Mock.mock( rurl?, rtype?, template|function( options ) )使用
- rurl: 当拦截到匹配
rurl
的 Ajax 请求时,将根据数据模板template
生成模拟数据,并作为响应数据返回 - rtype:当拦截到匹配
rtype
的 Ajax 请求时,将根据数据模板template
生成模拟数据,并作为响应数据返回。 - template:生成模拟数据的模板
- function: 当拦截到匹配
rurl
的 Ajax 请求时,函数function(options)
将被执行,并把执行结果作为响应数据返回。
具体的参见:https://github.com/ataola/node-blacksmith/tree/master/code/framework/koa-study/koa-mock/static
Mock在后端的使用
在前面我们了解了Mock在前端的使用,我们还需要思考这么一个问题,模拟也要模拟的深沉一点,也就是像一点,前面的写法足以应付大部分场景,但是有的时候我们需要拟合后端的服务,比如网络的延迟、跨域、性能等等问题,我们更加期望是搞一个服务器,模拟后端的一些API行为。作为前端选手,Javascript天然会,既然用了Javascript的基础,我们自然而然地会想到用Node.JS去搭建一个后端服务。
笔者这里使用Koa搭建一个后端服务,主体代码如下:
const http = require('http');
const Koa = require('koa');
const app = new Koa();
const logger = require('koa-logger');
const bodyparser = require('koa-bodyparser')
const onerror = require('koa-onerror');
const errorMiddleware = require('./middlewares/error');
const ipBlackListMiddleware = require('./middlewares/ip_blacklist');
const { host, port, ip_blacklist } = require('./config/index');
// import routes
const IndexRoute = require('./routes/index');
const MockRoute = require('./routes/mock');
// error handler
onerror(app);
app.use(ipBlackListMiddleware(ip_blacklist));
// when post, use x-www-form-urlencoded or json
app.use(bodyparser({
enableTypes:['json', 'form', 'text']
}));
// use koa-logger
app.use(logger());
// routes middleware
app.use(IndexRoute.routes(), IndexRoute.allowedMethods());
app.use(MockRoute.routes(), MockRoute.allowedMethods());
// error-handling
app.on('error', errorMiddleware());
// create a server
const server = http.createServer(app.callback());
// listen port
server.listen(port, host, () => {
console.log(`mock server is running in http://${host}:${port}`);
});
module.exports = server;
大致的一个流程是,导入了项目的npm包,中间件、路由,初始化Koa实例,调用了相关的中间件和路由,最后监听服务器端口。
如果对Mock不是很熟,我们大致会这样做,把相关返回信息写在JSON文件中或者js文件中,然后通过引入或者读取相关文件来做这件事
JSON文件形式
{
"data": {
"name": "zjt",
"age": 23
},
"success": true,
"code": 1,
"message": "获取用户信息成功"
}
定义完返回格式后,我们可以通过commonJS的语法用require引入,也可以通过内置的fs模块的读取文件的函数去读取这部分JSON的内容,然后把它衔接到相关路由上面构成一个Mock API。
形如:
const user_json = require('../mock/json/user.json');
router.get('/json/user', async ctx => {
ctx.body = user_json;
});
JS文件形式
这里仿照楼上也是类似的。
const user_js = require('../mock/js/user');
router.get('/js/user', async ctx => {
ctx.body = user_js;
});
这样做的话,能够满足我们日常生活中的大部分开发,但是太费劲了,每次我们都要写这么多一坨坨的JSON或者JS文件,我们希望这个Mock Server能够更加智能一点,本着”简单、短小精悍“的原则去思考,我们自然而然会想到引入Mock.JS去做这件事情。
Mock.JS的应用
这里我们思考一个例子,最常见的就以返回用户身份信息为例。我们就意思下,罗列一下用户常见的属性,比如说用户id、用户名字、用户昵称、用户生日、用户地址、用户邮箱、能量值(也可以理解成阳光值)、创建时间、更新时间
const Mock = require('mockjs');
const data = Mock.mock({
'data|4-10': [{
'id': '@id',
'name': '@cname',
'nickname|1': ['沉鱼', '落雁', '闭月', '羞花'],
'birthday': '@date',
'address': '@county(true)',
'email': '@email',
'power|1-5': '★',
'created': '@now',
'updated': '@now'
}]
});
module.exports = {
data,
success: true,
code: 1,
message: '获取用户数据成功'
};
简单的讲下
'data|4-10'[{}]
: 表示有个数组data,它里面至少有4个对象,上限10个对象@id
: 表示数据占位符定义,一个id@name
: 表示数据占位符定义,一个name'nickname|1': ['沉鱼', '落雁', '闭月', '羞花'],
: 表示nickname为一个字符串,值为沉鱼落雁闭月羞花中的一个。@date
: 表示数据占位符定义,一个形如1997-06-13
这样的日期@county(true)
: 表示数据占位符定义, 一个形如江苏省 淮安市 金湖县
这样的地址@email
: 表示数据占位符定义,一个邮箱'power|1-5': '★'
: 表示有个字符串,值为最少1颗星,最多5颗星,其实这个做外卖五分好评或者老师课程评价这种数据展示应景一些@now
: 表示当前时间。
最后的效果就是
这里我们可以看出Mock的结果还是有些不可控性,比如我就想让它显示正常点的邮箱、可读性强一点的段落文字,这里就要用到文中的沉鱼落雁闭月羞花的例子,我们事先准备好部分结果集让其Mock数据。
Mock数据的单元测试
这里我是结合Mocha(测试框架)、chai(断言)、supertest(模拟http测试)对Mock的API进行了一个单元测试,具体的如下:
const app = require('../server');
const supertest = require('supertest')(app);
const expect = require('chai').expect;
describe('mock Server', () => {
describe('#GET /', () => {
it('should return a response with HTTP code 200', function(done) {
supertest
.get('/')
.expect(200, done);
});
});
describe('#GET /mock/js/user', () => {
it('response data success should return true', (done) => {
supertest
.get('/mock/js/user')
.expect(200)
.end((err, res) => {
if (err) {
done(err);
}
const { code, data, message, success } = res.body;
expect(res.statusCode).to.equal(200);
expect(res.body).to.be.an('object');
expect(code).to.eql(1);
expect(data).to.eql({ name: 'ataola', skill: 'node.js' });
expect(message).to.eql('获取用户信息成功');
expect(success).to.eql(true);
done();
});
});
});
});
高配版的Mock Server
站在产品经理的角度,我想,高配版的Mock Server就是打开浏览器,有个界面给你点点点进行增删改查,然后生成一个API,有兴趣的童鞋可以去实现下,溜了溜了。。。
最后
本文选自“Node.JS打铁”系列文章,项目地址:https://github.com/ataola/node-blacksmith
文中涉及到的项目例子地址:https://github.com/ataola/node-blacksmith/tree/master/code/framework/koa-study/koa-mock
参考文献
Mock.JS官网: http://mockjs.com/
MockJS 文档:https://github.com/nuysoft/Mock/wiki
MockJS 示例:http://mockjs.com/examples.html
MockJS语法规范:https://github.com/nuysoft/Mock/wiki/Syntax-Specification
Mock.Mock()的使用:https://github.com/nuysoft/Mock/wiki/Mock.mock()