搭建前端mock層

什麼是mock?mock指的是模擬。前端

在軟件工程中當開發工做不一樣步狀況下爲了避免斷工做鏈,而模擬其中一環節讓工做得以持續下去,符合這種狀況的工做咱們就叫mock。固然咱們常說的mock是指模擬網絡請求先後端通信。vue

前端和後端的協同工做中,爲了避免串行工做流加快開發效率,每每須要制定協議,而前端只須要後端提供數據接口,因此這份協議就是接口協議。根據這份協議前端就能夠mock模仿數據,達到先後端並行開發。node

mock能夠分爲如下幾種方式:webpack

  1. 侵入業務代碼,設置開關
  2. 利用webpack構建工具攔截請求
  3. 搭建mock服務層代理響應
  4. 利用瀏覽器插件攔截請求

cms-web是由vue-cli3腳手架搭建的項目,依賴webpack打包工具,能夠利用devServer.defore api對請求攔截處理,返回mock數據,引入mock層。git

因此選擇上面第二種方式,緣由總結爲:無侵入,簡單快捷易上手github

mockjs

藉助mockjs實現mock數據邏輯,如下是最簡單的實現:web

var Mock = require('mockjs');
...
before(app, server) {
    app.get('/api/action/game/list', function (req, res, next) {
      var data = Mock.mock({
        code: 0,
        data: {
          'totalCount|1-100': 1,
          'datas|1-10': [{
            'id|+1': 1,
            'dataName|1-5': 'name',
            'dataType|1-2': 1,
            'dataScope': '1,100',
            'defaultValue|1-5': 'value',
            'dataStatus|0-1': 0,
            'remark|1-5': 'str',
            'record|0-1': 0,
            'series|1-3': [{
              'id|+1': 1,
              'type|1-2': 2,
              'name|1-2': '小云熊奇妙故事',
              'mark': null,
              'status': 0
            }]
          }]
        }
      })
    
      res.json(data)
    })
}
複製代碼

這裏咱們簡單介紹一下mockjs的使用規範,詳細移至mockjs文檔ajax

數據模板中屬性主要由三個部分構成:屬性名,生成規則和屬性值chrome

'屬性名|生成規則': 屬性值
複製代碼

以'dataName|1-5': 'name'爲例,dataName是屬性值,name是屬性值,1-5是生成規則,對應String類型屬性表明重複隨機次數1到5次,假如對應Number類型表明生成隨機數的最小值1和最大值5。'id|+1': 1表示id值會從開始疊加。vue-cli

優化mock層

話題回來mock層搭建的迭代優化,首先分析上面實現存在的問題:

  • 全部的請求都會通過這一層,mock層匹配到請求就會被攔截,須要設置開關
  • 配置的請求和數據模板會愈來愈多,邏輯與配置混合會致使代碼愈來愈臃腫,邏輯和配置要分離

把mock層從before函數中抽離,並設置開關機制:

var Mock = require('mockjs');

class MockHandler {
  constructor() {}
  
  handle(app, server) {
    app.get('/api/action/game/list', function (req, res, next) {
      if (!!process.env.mock || !!req.query._is_mock) {
        var data = Mock.mock({
          code: 0,
          data: {
            'totalCount|1-100': 1,
            'datas|1-10': [{
              'id|+1': 1,
              'dataName|1-5': 'name',
              'dataType|1-2': 1,
              'dataScope': '1,100',
              'defaultValue|1-5': 'value',
              'dataStatus|0-1': 0,
              'remark|1-5': 'str',
              'record|0-1': 0,
              'series|1-3': [{
                'id|+1': 1,
                'type|1-2': 2,
                'name|1-2': '小云熊奇妙故事',
                'mark': null,
                'status': 0
              }]
            }]
          }
        });
        res.json(data);
      } else {
        next();
      }
    })
  }
}

module.exports = new MockHandler(config);
複製代碼

設置了兩個並聯開關,分別設置環境變量mock或者往請求查詢參數加入_is_mock變量均可以經過驗證。

邏輯與配置分離:

const config = {
    '/api/action/game/list': {
      code: 0,
      data: {
        'totalCount|1-100': 1,
        'datas|1-10': [{
          'id|+1': 1,
          'dataName|1-5': 'name',
          'dataType|1-2': 1,
          'dataScope': '1,100',
          'defaultValue|1-5': 'value',
          'dataStatus|0-1': 0,
          'remark|1-5': 'str',
          'record|0-1': 0,
          'series|1-3': [{
            'id|+1': 1,
            'type|1-2': 2,
            'name|1-2': '小云熊奇妙故事',
            'mark': null,
            'status': 0
          }]
        }]
      }
    }
}

class MockHandler {
  constructor(config) {
      this.config = config
  }
  
  handle(app, server) {
    Object.keys(this.config).forEach(route => {
        app.get(route, function (req, res, next) {
            if (!!process.env.mock || !!req.query._is_mock) {
                var data = Mock.mock({
                  code: 0,
                  data: {
                    'totalCount|1-100': 1,
                    'datas|1-10': [{
                      'id|+1': 1,
                      'dataName|1-5': 'name',
                      'dataType|1-2': 1,
                      'dataScope': '1,100',
                      'defaultValue|1-5': 'value',
                      'dataStatus|0-1': 0,
                      'remark|1-5': 'str',
                      'record|0-1': 0,
                      'series|1-3': [{
                        'id|+1': 1,
                        'type|1-2': 2,
                        'name|1-2': '小云熊奇妙故事',
                        'mark': null,
                        'status': 0
                      }]
                    }]
                  }
                })
                res.json(data);
            } else {
                next();
            }
        })
    })
  }
}
複製代碼

革命還沒有成功,舊的問題的解決了,但同時又出現的新的問題:

  • 只支持get請求,不支持post請求
  • get方法的回調函數中重複了不少次對mock開關條件的判斷,須要提取出來

針對第一個問題,只須要將get/post請求方法配置到route就行了,自己屬於配置邏輯的範圍以內。而第二個問題,能夠提煉函數,利用函數式編程,利用高階函數返回回調函數,這樣代碼會簡潔不少,具體優化以下:

const config = {
    'GET /api/action/game/list': {
      code: 0,
      data: {
        'totalCount|1-100': 1,
        'datas|1-10': [{
          'id|+1': 1,
          'dataName|1-5': 'name',
          'dataType|1-2': 1,
          'dataScope': '1,100',
          'defaultValue|1-5': 'value',
          'dataStatus|0-1': 0,
          'remark|1-5': 'str',
          'record|0-1': 0,
          'series|1-3': [{
            'id|+1': 1,
            'type|1-2': 2,
            'name|1-2': '小云熊奇妙故事',
            'mark': null,
            'status': 0
          }]
        }]
      }
    }
}

class MockHandler {
  constructor(config) {
    this.init(config);

    this.config = config;
  }
  init(config) {
    Object.keys(config).forEach(route => {
      const model = config[route];

      function callbackWarpper(model) {
        return function (req, res, next) {
          if (isMock || !!req.query._is_mock) {
            var data = Mock.mock({
              code: 0,
              data: model
            });
            res.json(data);
          } else {
            next();
          }
        };
      }
      config[route] = callbackWarpper(model);
    });
  }
  handle(app, server) {
    for (let route in this.config) {
      let [method, path] = route.split(' ')
      let reg = new RegExp(path);
      let callback = this.config[route];
      app[method.toLowerCase()](reg, callback);
    }
  }
}
複製代碼

寫到這裏本覺得本身寫出了一手漂亮的代碼,可是忽然想起,app實例上有個use()能夠用來忽略請求方法get/post,而後在內部對請求作匹配處理,這樣代碼會更加的精簡和容易被理解:

const isMock = !!process.env.mock;
const config = {
    '/api/action/game/list': {
      code: 0,
      data: {
        'totalCount|1-100': 1,
        'datas|1-10': [{
          'id|+1': 1,
          'dataName|1-5': 'name',
          'dataType|1-2': 1,
          'dataScope': '1,100',
          'defaultValue|1-5': 'value',
          'dataStatus|0-1': 0,
          'remark|1-5': 'str',
          'record|0-1': 0,
          'series|1-3': [{
            'id|+1': 1,
            'type|1-2': 2,
            'name|1-2': '小云熊奇妙故事',
            'mark': null,
            'status': 0
          }]
        }]
      }
    }
}

class MockHandler {
  constructor(config) {
    this.config = config;
  }
  handle(app, server) {
    app.use((req, res, next) => {
      if (isMock || !!req.query._is_mock) {
        Object.keys(this.config).forEach(route => {
          const reg = new RegExp(route)
          const model = this.config[route]
          if (reg.test(req.path)) {
            var data = Mock.mock({
              code: 0,
              data: model
            });
            return res.json(data);
          }
        })
        next()
      } else {
        next()
      }
    })
  }
}
複製代碼

這個方案原理是利用webpack devServer在發送請求時候作了一層攔截,請求並無通過真實的網絡環境,可是利用webpack devServer其實有個弊端,當我想增長一條記錄時,須要再次重啓webpack,自己屬於對webpack配置的修改,除利用node watch功能監聽文件改動運行腳本重啓的方法外,幾乎想不出有更好的方法了,這個痛點用一個名詞來形容就是不支持熱插拔。

瀏覽器插件

有沒有不涉及到編程的mock方案呢?有,利用瀏覽器插件攔截請求或篡改響應。

這種方案屬於利用工具,不用在代碼層面引入mock層也能夠攔擊請求,好處是相對前面介紹的方案,更加的具備普適性,可讓測試小夥伴在後端接口發生異常的狀況下依然暢通無阻的測試前端功能。

這樣的插件我推薦: Ajax Interceptor 下載 源代碼

工具

不想用瀏覽器插件還有其餘的工具嗎?固然有,就是咱們熟悉的postman,詳細請參考下面文章:

Postman高級應用(11):能夠開始對接了嗎——Mock服務

相關文章
相關標籤/搜索