中后台Web前端项目使用的是vue-element-admin[文档:
https://panjiachen.github.io/vue-element-admin-site/zh/],此脚手架集成了vue-cli和Element[文档:
https://element.eleme.cn/#/zh-CN]
我在开中集成了本地Mock的功能,此依赖作用巨大。这样只要有接口规范文档,无需启动后端服务,并且不需要发送真实请求。
点击请求登录
请求记录可查看,非常有用
项目的迭代比较少,为了稳定性,我加入了单元测试。采用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/