搭建一個低配版的Mock Server
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種數據定義寫法。具體的參見://mockjs.com/examples.html
使用Mock的背景
我們為什麼要使用Mock?
傳統的非前後端分離的項目,後端老哥除了要做對接伺服器資料庫相關的工作,還要搞前端頁面,太多太累太雜了。隨著時代的發展、人類社會的進步,編程技術的更新迭代,慢慢地開始有了專職的前端程式設計師和後端程式設計師等等,項目越來越複雜,前後端的要求度逐步提高,尤其是Node.JS技術的迅猛發展,十一年彈指一揮間,在npm、github各類庫和項目如雨後春筍般蹭蹭蹭地雄起,給開發者提供了很多解決方案,這也使得前後端分離成為可能。事物存在的即是合理的,但我們也要辯證地去看待這個事物。前後端分離項目的落地比前後端不分離的落地增加了開發人員對接溝通的成本,在某些場景下,前端開發會受限制於後端開發,接地氣地說就是後端介面沒寫好沒提供前端可能就無從下手了,為了解決這個問題,我們需要進行相關地Mock,來模擬後端返回的數據也好或者後端的介面也好,總之,我們需要一個Mock Server。
Mock在前端的使用
安裝
# npm安裝
npm i mockjs -D
引入方式
傳統script腳本引入
去Bootcdn引入相關的腳本,地址://www.bootcdn.cn/Mock.js/, 形如
<!-- 生產環境 -->
<script src="//cdn.bootcdn.net/ajax/libs/Mock.js/0.1.1/mock-min.js"></script>
<!-- 開發環境 -->
<script src="//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)
將被執行,並把執行結果作為響應數據返回。
具體的參見://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 //${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打鐵」系列文章,項目地址://github.com/ataola/node-blacksmith
文中涉及到的項目例子地址://github.com/ataola/node-blacksmith/tree/master/code/framework/koa-study/koa-mock
參考文獻
Mock.JS官網: //mockjs.com/
MockJS 文檔://github.com/nuysoft/Mock/wiki
MockJS 示例://mockjs.com/examples.html
MockJS語法規範://github.com/nuysoft/Mock/wiki/Syntax-Specification
Mock.Mock()的使用://github.com/nuysoft/Mock/wiki/Mock.mock()