mock翻譯過來是模仿的意思,Server是伺服器。粗暴點直譯就是模仿伺服器。
寫在前面
通過閱讀本文,你將對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()