vue-cli/element/vue-element-admin/mock/反向代理/单元测试/Jest/vue-test-utils

中后台Web前端项目使用的是vue-element-admin[文档: https://panjiachen.github.io/vue-element-admin-site/zh/],此脚手架集成了vue-cli和Element[文档: https://element.eleme.cn/#/zh-CN]

我在开中集成了本地Mock的功能,此依赖作用巨大。这样只要有接口规范文档,无需启动后端服务,并且不需要发送真实请求。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H1qwDgXx-1612330624989)(/Users/hellocheng09/Library/Application Support/typora-user-images/image-20210203103758410.png)]

点击请求登录

在这里插入图片描述
在这里插入图片描述

请求记录可查看,非常有用

项目的迭代比较少,为了稳定性,我加入了单元测试。采用Jest以及vue官方提供的vue-test-utils。过程中遇到很多报错,努力一一解决

在这里插入图片描述

目前实现了基本的测试需求:1、不影响原有本地模拟功能,并且在原有模拟基础数据上做单测。2、重点自动化测试,点击出ElMessageBox,然后测试请求发送和接收情况。很多设备的操作都需要这个操作,实际上线问题缺陷最多的点。

/**
 * demo.spec.js
 *
 * 2021-2-1
 * Wallace Chan
 */

import axios from '@/utils/request' // 引入改造后的axios
import $ from 'jquery' // 开发依赖 引入jq便于断言选择、操作元素
import localVue from './common' // 引入单测的公共配置
import { mount } from '@vue/test-utils'
import TestDemo from '@/views/test/demo' // 引入组件
import flushPromises from 'flush-promises' // 开发依赖 官方推荐依赖,处理异步请求的测试
const system = require('../../mock/system-manage') // mockJs的前端本地的数据,这样倒入,修改mock数据也无需修改单测断言

const mockRes = system[system.length - 1].baseInfo
const { name, age } = mockRes.queryResult.data

jest.setTimeout(30000) // 默认超时间5秒,修改为30秒,根据实际需求设置

beforeEach(() => {
  axios.get.mockClear()
  axios.get.mockReturnValue(Promise.resolve({}))
})

// 非常重要,反向代理的所有配置都要jest mock
// 此处处理消耗了很多时间调试,最终才理解
jest.mock('@/utils/request', () => {
  return {
    create: jest.fn(),
    interceptors: {
      request: { use: jest.fn() },
      response: { use: jest.fn() }
    },
    get: jest.fn()
  }
})

// 正式单测内容
describe('测试请求和渲染 >>>>', () => {
  // 非常重要 涉及内部有异步的情况,需要加上done参数
  test('有确认弹出的请求', (done) => {
    // 将模拟数据挂载
    axios.get.mockReturnValue(Promise.resolve(mockRes))

    const wrapper = mount(TestDemo, {
      localVue
    })

    // 非常非常重要
    // 此处的弹出提示直接在body元素下,无法通过wrapper获取
    // jq的事件无法像vue-test-utils提供的trigger返回Promise,
    // jq的事件需要这样处理才生效
    // 加了setTimeout那必须加入done,否则异步内部不会执行
    setTimeout(async() => {
      await wrapper.find('#btn').trigger('click')
      expect($('.confirmBtn').length).toBe(1)
      const elementBtn = $('body .confirmBtn')
      elementBtn.click()
      await flushPromises()
      await wrapper.vm.$nextTick()
      expect(wrapper.find('#box').text()).toBe(`His name is  ${name}`)

      done()
    }, 1000)
  })

  test('无确认,直接发送请求', async() => {
    axios.get.mockReturnValue(Promise.resolve(mockRes))

    const wrapper = mount(TestDemo, {
      localVue
    })

    // 点击直接发送请求
    // 这种处理就现对简单了
    // 无需加done
    await wrapper.find('#btn2').trigger('click')
    await flushPromises()
    await wrapper.vm.$nextTick()
    expect(wrapper.find('#box').text()).toBe(`${age} year old !`)
  })

  test('快照', () => {
    const wrapper = mount(TestDemo, {
      localVue
    })

    // 简单的生成一个快照
    expect(wrapper).toMatchSnapshot()
  })
})

/**
 * common.js
 *
 * 2021-2-1
 * Wallace Chan
 */

import { createLocalVue } from '@vue/test-utils'
const localVue = createLocalVue()
import ElementUI from 'element-ui'
import axios from 'axios'
localVue.prototype.axios = axios

localVue.use(ElementUI)

export default localVue
I'm I'm 
/**
 * request.js
 *
 * 2021-2-1
 * Wallace Chan
 */
import Axios from 'axios'
import router from '@/router'
import { MessageBox, Notification } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
import clipboard from '@/utils/clipboard'

let messageEr = null
window.tokenlostEr = null
const isshow = true

// 初始化axios请求
const axios = Axios.create({
  baseURL: process.env.VUE_APP_BASE_API,
  timeout: 40000
})

axios.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8'

function showerror(url, error,) {
  url = url.replace(/\s*/g, '')
  const apiRoot = url.split('/')[1]

  if (error.response && error.response.status === 401 && !window.tokenlostEr) {
    router.push('/login/')
    window.tokenlostEr = Notification({
      type: 'warning',
      title: 'A Token Error',
      message: '登录超时,请重新登录!'
    })
  } else if (!messageEr || isshow && !window.tokenlostEr) {
    messageEr =
    Notification({
      duration: 8000,
      position: 'bottom-right',
      customClass: 'c_alert_box api_color',
      offset: 1,
      dangerouslyUseHTMLString: true,
      message:
      error.code === 99999 || !error.code
        ? `<div class="api_wrong">
          <div class="flex api_title"  >
            <img class="icon_img" src="${require('@/assets/img/linkbroken.png')}" />
            <span style="font-weight: bold;color:#333">
                后端${apiRoot}服务异常或未成功启动。
            </span>
          </div>
          <div style="color:#333">
            <div class="alert_content">点击复制异常地址:</div>
            <div class="url_box" >${url}</div>
          </div>
      </div>
      ` : `<div class="api_wrong">
      <div style="color:#333">
        <div class="alert_content">点击复制异常地址:</div>
        <div class="url_box" >${url}</div>
        <div class="alert_content">错误码:${error.code}</div>
        <div class="alert_content">错误提示:${error.message}</div>
      </div>
  </div>
  `,
      // <div class="alert_content">错误提示:${error.message}</div>
      onClose() {
        messageEr = null
      },
      onClick() {
        // eslint-disable-next-line no-caller
        var e = window.event || arguments.callee.caller.arguments[0]
        clipboard(url, e, '异常接口地址复制成功!')
      }
    })
  }
}

// 请求拦截
axios.interceptors.request.use(
  config => {
    // token配置
    if (store.getters.token) {
      config.headers.Authorization = getToken() && ('Bearer ' + getToken())
    }

    return config
  },
  error => {
    console.log(error)
    return Promise.reject(error)
  }
)

// 回传拦截
axios.interceptors.response.use(
  response => {
    const res = response.data
    // 回传参数处理
    if (res.code !== 200) {
      if (res.code === 10001 || res.code === 10002) {
        MessageBox.confirm(res.message, '提示', {
          confirmButtonText: '重新登陆',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          store.dispatch('user/resetToken').then(() => {
            location.reload()
          })
        })
      }
      return Promise.reject(res)
    } else {
      return res
    }
  },
  error => {
    // Message({
    //   message: '数据请求失败!',
    //   type: 'error',
    //   duration: 5 * 1000
    // })
    return Promise.reject(error)
  }
)

export default axios

// const Vue1 = new Vue()
// const h = Vue1.$createElement

export function post(url, params) {
  return new Promise((resolve, reject) => {
    axios
      .post(url, params)
      .then(res => {
        resolve(res)
      })
      .catch(error => {
        if (error.code !== 23005) {
          showerror(url, error)
        }
        reject(error)
      })
  })
}

export function patchData(url, params) {
  return new Promise((resolve, reject) => {
    axios
      .patch(url, params)
      .then(res => {
        resolve(res)
      })
      .catch(error => {
        showerror(url, error)

        reject(error)
      })
  })
}

export function deleteData(url, params) {
  return new Promise((resolve, reject) => {
    axios
      .delete(url, {
        params: params
      })
      .then(res => {
        resolve(res)
      })
      .catch(error => {
        showerror(url, error)
        reject(error)
      })
  })
}

export function get(url, params) {
  return new Promise((resolve, reject) => {
    axios
      .get(url, {
        params: params
      })
      .then(res => {
        resolve(res)
      })
      .catch(error => {
        showerror(url, error)

        reject(error)
      })
  })
}

/**
 * system-manage.js
 *
 * 2021-2-1
 * Wallace Chan
 */

const baseInfo = { 'success': true, 'code': 200, 'message': 'mock axios data!', 'queryResult': { 'data': { name: 'Jackie Chan', age: 'Fifteen' }, 'total': 0 }
}

const layout = { 'success': true, 'code': 200, 'message': '操作成功!' }
module.exports = [
  {
    url: '/auth/userlogout',
    type: 'post',
    response: config => {
      return layout
    }
  },
  {
    url: '/manager/stationInfo/getStationInfo',
    type: 'get',
    response: config => {
      return baseInfo
    }
  }, {
    baseInfo
  }
]


抛砖引玉,如果对你有所帮助那就再好不过

🍼🍑⭐ 代码下载: https://gitee.com/hellocheng0903/vue-test-demo/tree/c8cdac0449530f923c316fb5e7bc1b457332c5dd/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值