Mock顧名思義是一種模擬。一般利用相同的接口來模擬出一個對象以代替真實對象,這樣能有效隔離外部依賴,便於測試。對於前端開發,Mock做爲重要一環,能帶來不少好處:javascript
前端開發可簡單分爲三個階段:並行開發階段、聯調階段和測試階段。如今的前端項目大多爲先後端分離,在開發、聯調階段不可避免要面對數據源的問題。前端
在聯調階段,各個環境已有真實數據,方便本地調試,咱們通常會將接口指向真實數據源。若是有跨域限制的話,可利用Charles、Fiddler等調試代理工具來解決,也能夠起一個本地Server:java
const express = require('express');
const proxy = require('http-proxy-middleware');
const app = express();
app.use('/api', proxy({ target: 'your-api-url', changeOrigin: true }));
app.listen(3000);
複製代碼
若是還處在並行開發階段,那咱們就須要Mock數據,通常有如下幾種經常使用方式:node
一、攔截Ajax、Fetch請求
缺點:前端混入髒代碼;沒法有效模擬網絡狀況。git
二、本地Mock Server
缺點:接口衆多,建立和修改爲本高。github
三、YApi、Easy Mock的接口管理平臺
缺點:靈活性不夠。好比一些配置信息分散在各個接口,無法集中管理,修改爲本高。express
本文以筆者接觸較多的Swagger爲例,從一個側面改善本地Mock Server須要不斷建立接口的缺點。打開後端提供的Swagger UI地址的Network,發現有個api-docs文件。npm
這個JSON文件包含接口、請求方法、響應格式等信息。能夠想看法析這個文件並不難,惟一比較麻煩的可能就是響應值的解析和類型轉換。若是能適時同步數據到本地Mock Server,能省去很多乏味的體力活。json
前面咱們提到解析JSON文件的難點主要在響應值類型的轉換,這邊咱們利用Easy Mock的一個解析模塊來作這件事情。後端
const swaggerParserMock = require('swagger-parser-mock');
const synchronizeSwagger = {
init({ url, blacklist, outputPath }) {
this.url = url;
this.blacklist = blacklist;
this.outputPath = outputPath;
this.parse();
},
async parse() {
const { paths } = await swaggerParserMock(this.url);
this.generate(paths);
console.log(paths);
}
}
synchronizeSwagger.init({
// Swagger api-docs地址
"url": "your-api-docs-url",
// 輸出目錄
"outputPath": "./routes",
// 黑名單,跳過一些不須要同步的api
"blacklist": []
});
複製代碼
打印paths信息,格式大體以下:
"/path/foo": {
"get": {
"summary": "bar",
"responses": {
"200": {
"example": "'@string'" // 模塊爲咱們作的類型轉化和Mock.js包裝。
}
}
},
"post": {
"summary": "baz",
"responses": {
"200": {
"example": "'@string'"
}
}
}
}
複製代碼
const fs = require('fs');
const { join } = require('path');
const { promisify } = require('util');
const mkdirp = require('mkdirp');
const writeFile = promisify(fs.writeFile);
const mkdir = promisify(mkdirp);
const synchronizeSwagger = {
// 遍歷api path信息
traverse(paths) {
for (let path in paths) {
if (this.blacklist.includes(path)) {
continue;
}
for (let method in paths[path]) {
const pathInfo = paths[path][method];
if (!pathInfo['responses']['200']) {
continue;
}
this.generate(path, method, pathInfo);
}
}
}
}
複製代碼
const synchronizeSwagger = {
// 生成mock文件
async generate(path, method, pathInfo) {
const outputPath = join(__dirname, this.outputPath, path);
const {
summary,
responses: { 200: responseOK },
} = pathInfo;
try {
// 生成目錄
await mkdir(outputPath);
const example = responseOK['example'];
// 生成文件內容
const template = this.generateTemplate({
summary,
example,
method,
path,
});
// 生成文件, 已存在的跳過,避免覆蓋本地以及編輯的文件
const fPath = join(outputPath, `${method}.js`);
await writeFile(fPath, template, { flag: 'wx' });
console.log(`增長Mock文件:${fPath}`);
} catch (error) {
/* eslint-disable no-empty */
}
},
generateTemplate({ summary, example, method, path }) {
// prettier-ignore
// api path中的{petId}形式改成:petId
return `/** ${summary} **/ const Mock = require("mockjs"); module.exports = function (app) { app.${method}('/api${path.replace(/\{([^}]*)\}/g, ":$1")}', (req, res) => { res.json(Mock.mock(${example})); }); };`;
},
}
複製代碼
以express爲例,利用require動態特徵咱們來建立路由,映射到剛纔建立的接口文件。
const fs = require('fs');
const join = require('path').join;
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
app.listen(port, function() {
console.log(`server is listening ${port}`);
});
function scan(path, app) {
const files = fs.readdirSync(path);
for (let i = 0; i < files.length; i++) {
const fpath = join(path, files[i]);
const stats = fs.statSync(fpath);
if (stats.isDirectory()) {
scan(fpath, app);
}
if (stats.isFile()) {
require(fpath)(app);
}
}
}
scan(join(__dirname, './routes'), app);
複製代碼
至此咱們就利用Swagger UI同步Mock數據,若是再加上cors、body-parser等Middleware,一個本地Mock Server基本成形。方便同步,咱們將它加入npm scripts。
"scripts": {
"ss": "node ./synchronizeSwagger.js"
},
複製代碼
執行npm run ss,就能生成相應的Mock數據和訪問接口了。
附件:示例代碼