项目实训 week1 为项目搭建mock.js模拟响应数据

mock.js的用处是增加单元测试的真实性,通过随机数据,模拟各种场景。不需要修改既有代码,就可以拦截 Ajax 请求,返回模拟的响应数据。

最关键的一点是不需要等待后端接口,可以自己模拟响应的数据来开发前端

这无疑会给前端开发带来方便

mock.js的安装

运行

npm install mockjs

在项目根目录新建mock文件夹,与package.json同级
项目实训 week1 为项目搭建mock.js模拟响应数据

在mock文件夹下新建index.js

const Mock = require('mockjs')
const { param2Obj } = require('./utils')

const test = require('./test')

const mocks = [
  ...test,
]


function mockXHR() {

  Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
  Mock.XHR.prototype.send = function() {
    if (this.custom.xhr) {
      this.custom.xhr.withCredentials = this.withCredentials || false

      if (this.responseType) {
        this.custom.xhr.responseType = this.responseType
      }
    }
    this.proxy_send(...arguments)
  }

  function XHR2ExpressReqWrap(respond) {
    return function(options) {
      let result = null
      if (respond instanceof Function) {
        const { body, type, url } = options
        // https://expressjs.com/en/4x/api.html#req
        result = respond({
          method: type,
          body: JSON.parse(body),
          query: param2Obj(url)
        })
      } else {
        result = respond
      }
      return Mock.mock(result)
    }
  }

  for (const i of mocks) {
    Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response))
  }
}

module.exports = {
  mocks,
  mockXHR
}

为了不让mock.js每次修改的时候都需要重新打包,我们可以写一个mock-server热修改功能

const chokidar = require('chokidar')
const bodyParser = require('body-parser')
const chalk = require('chalk')
const path = require('path')
const Mock = require('mockjs')

const mockDir = path.join(process.cwd(), 'mock')

function registerRoutes(app) {
  let mockLastIndex
  const { mocks } = require('./index.js')
  const mocksForServer = mocks.map(route => {
    return responseFake(route.url, route.type, route.response)
  })
  for (const mock of mocksForServer) {
    app[mock.type](mock.url, mock.response)
    mockLastIndex = app._router.stack.length
  }
  const mockRoutesLength = Object.keys(mocksForServer).length
  return {
    mockRoutesLength: mockRoutesLength,
    mockStartIndex: mockLastIndex - mockRoutesLength
  }
}

function unregisterRoutes() {
  Object.keys(require.cache).forEach(i => {
    if (i.includes(mockDir)) {
      delete require.cache[require.resolve(i)]
    }
  })
}

// for mock server
const responseFake = (url, type, respond) => {
  console.log(new RegExp(`/dev-api/${url}`));
  return {
    url:new RegExp(`/dev-api/${url}`),
    type: type || 'get',
    response(req, res) {
      console.log('request invoke:' + req.path)
      res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond))
    }
  }
}

module.exports = app => {
  // parse app.body
  // https://expressjs.com/en/4x/api.html#req.body
  app.use(bodyParser.json())
  app.use(bodyParser.urlencoded({
    extended: true
  }))

  const mockRoutes = registerRoutes(app)
  var mockRoutesLength = mockRoutes.mockRoutesLength
  var mockStartIndex = mockRoutes.mockStartIndex

  // watch files, hot reload mock server
  chokidar.watch(mockDir, {
    ignored: /mock-server/,
    ignoreInitial: true
  }).on('all', (event, path) => {
    if (event === 'change' || event === 'add') {
      try {
        // remove mock routes stack
        app._router.stack.splice(mockStartIndex, mockRoutesLength)

        // clear routes cache
        unregisterRoutes()

        const mockRoutes = registerRoutes(app)
        mockRoutesLength = mockRoutes.mockRoutesLength
        mockStartIndex = mockRoutes.mockStartIndex

        console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed  ${path}`))
      } catch (error) {
        console.log(chalk.redBright(error))
      }
    }
  })
}

定义工具类util.js

/**
 * @param {string} url
 * @returns {Object}
 */
function param2Obj(url) {
  const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
  if (!search) {
    return {}
  }
  const obj = {}
  const searchArr = search.split('&')
  searchArr.forEach(v => {
    const index = v.indexOf('=')
    if (index !== -1) {
      const name = v.substring(0, index)
      const val = v.substring(index + 1, v.length)
      obj[name] = val
    }
  })
  return obj
}

module.exports = {
  param2Obj
}

mock.js的使用

我们首先在mock文件夹下编写test.js,用于测试接口

const testApi1 = {
  "userList|10":[
    {
      "name":"@cname",
      "age|18-28":25,
      "sex|1":["男","女"],
    }],
}

module.exports = [
  {
    url: 'data/testApi1',
    type: 'get',
    response: config => {
      // const { username } = config.body
      // const token = tokens[username]
      return {
        code: 200,
        data: testApi1
      }
    }
  },

]

这里我们拦截data/testApi1的get请求,并返回数据

数据的格式是随机生成的10个对象,包含人名、年龄、性别等。

找到request.js,将axios的基本路径保持和mock-server的监听基本路径一致

import axios from 'axios'

export function request(config) {
  return new Promise(((resolve, reject) => {

    const instance = axios.create({
      baseURL: "/dev-api/", //基本路径
      timeout: 1000,
    });

    instance(config).then(res => {
      resolve(res);
    }).catch(err => {
      reject(err);
    })
  }))

}

之后修改vue.config.js文件

module.exports = {
  // 输出目录
  assetsDir: 'static',
  devServer: {
    port: 8080,
    open: true,
    overlay: {
      warnings: false,
      errors: true
    },
    before: require('./mock/mock-server.js')
  },
};

需要添加devServer:{before: }选项,让mock.js起到拦截作用

最后我们编写一个请求,模拟向后端请求数据

getData() {
  request({
    url: 'data/testApi1',
    method: 'get',
  }).then(res => {
    let {code, data} = res.data;
    console.log(data);
    this.testData = data.userList;
  }).catch(err => {
    console.log("fail")
  });
}

最终效果:

项目实训 week1 为项目搭建mock.js模拟响应数据

上一篇:《从Lucene到Elasticsearch全文检索实战》的P184页


下一篇:python文档2-unittest单元测试之mock.patch