简单的事情往往被忽略,说到单元测试,很多团队都只是在接口部分加入,UI层面最容易被忽略。今天我将围绕一个简单的demo写一下ui层面的单元测试。该文章仅仅面对没接触UI层面单元测试,且想快速上手的人,大佬可将鼠标移到右上角点击x即可。
👓 前端做单元测试(what? why?)
- 模拟用户操作,先于用户发现问题。
👇 需要提前了解什么知识?准备什么?
- 像char、mocha、jest这个库倒是可以简单了解一下
- 如果你想快速上手跟着我敲一遍代码也足够了
- 在接下来的测试中,vuecli已经提供了@vue/test-utils库
- 由于个人习惯,我另外安装了chai的断言库(ts项目需要另外安装@types/chai)
👏 先来看看我要测试的这个demo
非常简单,但是“基本能覆盖”一般ui层面的测试,考虑哪些会在我们测试范围内?
- 点击“打开嵌套表单的Dialog”按钮,弹窗是否能弹出
- 输入的值是否符合我们想要的类型(正常来说是在点击确定按钮之后去核验)
- 点击确定按钮和取消按钮是否能关闭弹窗
😾 通过代码来看看测试的细节
- 需要测试的组件
// 需要测试的组件
<template>
<div class="about">
<el-button type="text" @click="dialogFormVisible = true">打开嵌套表单的 Dialog</el-button>
<el-dialog title="收货地址" :visible.sync="dialogFormVisible">
<el-form :model="form">
<el-form-item label="姓名" :label-width="formLabelWidth">
<el-input v-model="form.name" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="年龄" :label-width="formLabelWidth">
<el-input v-model="form.age" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="dialogFormVisible = false">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator';
@Component
export default class About extends Vue {
private dialogFormVisible = false;
private form = {
name: '',
age: 20,
};
private formLabelWidth = '120px';
}
</script>
- 先来看一下测试用例的具体代码,具体解释在代码中体现
import {mount, config, createLocalVue } from '@vue/test-utils';
import About from '@/views/About.vue';
import Vue from 'vue'
import ElementUI from 'element-ui'
import { expect } from 'chai'
// 引入elementui,不对全局的 Vue 构造函数注入任何东西。我们可以使用 createLocalVue 方法来存档它们:
const localVue = createLocalVue()
localVue.use(ElementUI)
config.stubs!.transition = false
describe('about.vue', () => {
// 由于在接下来的几个测试中都会用到同一个容器,所以仅需将vue挂载一次就可以了
const wrapper = mount(About, {
localVue
})
// 默认我们将使用async,await模式
it('dialog pop', async () => {
const dialog = wrapper.find('.el-dialog__wrapper') // 拿到弹窗
const openBtn = wrapper.findAll('.el-button').at(0) // 拿到触发弹窗显示的按钮
expect(dialog.attributes().style).contain('display') // 因为是elementui内部组件本有特性,这步可省略
expect(wrapper.vm.$data.dialogFormVisible).to.be.eq(false) // 控制弹窗的变量初始值为false
await openBtn.trigger('click') // 触发按钮的单击事件
expect(dialog.attributes().style).to.not.contain('display') // 同上可省略
expect(wrapper.vm.$data.dialogFormVisible).to.be.eq(true) // 控制弹窗的变量值是否变为true
});
// 对值的检查
it('check value', async () => {
const openBtn = wrapper.findAll('.el-button').at(0)
await openBtn.trigger('click')
// 模拟输入值
await wrapper.setData({
form: {
name: 'xiaomin',
age: 30
}
})
expect(typeof(wrapper.vm.$data.form.name)).to.be.eq('string') // 判断输入的值是否输入成功,是否为提前设置的类型
expect(typeof(wrapper.vm.$data.form.age)).to.be.eq('number') // wrapper.vm.$data可拿到所有的变量
})
// 判断弹窗是否能正常关闭
it('dialog close', async () => {
const openBtn = wrapper.findAll('.el-button').at(0)
await openBtn.trigger('click')
expect(wrapper.vm.$data.dialogFormVisible).to.be.eq(true)
const closeBtn = wrapper.findAll('.el-button').at(1) //根据dom位置,拿到取消按钮,并触发
await closeBtn.trigger('click')
expect(wrapper.vm.$data.dialogFormVisible).to.be.eq(false)
})
});
- 让我们看看上面代码中这个wrapper的dom,方便更好的理解。
console.log(wrapper.html())
-
现在跑一下这个测试(npm run test:unit)
-
踩的两个坑!
Can't find stylesheet to import
- 相对路径改为绝对路径即可
TypeError: Cannot read property '$el' of undefined"
- 这是由Vue Test Utils默认添加的同步Transition存根组件引起的。 您可以使用存根选项将其关闭
config.stubs!.transition = false
😼 总结
- 无论是前端还是后端,对单元测试的书写,也是对自己代码的一次检查,所以强烈建议对单元测试的重视
- 本文仅代码个人观点,若有写的不合适的地方,欢迎批评指正。