前言
单元测试的意义不再赘述,只要你看过各个框架、知名库的源码,都会涉及单元测试,这也是一个前端工程师的基本功,我们有必要学习。
起步
因为我目前用vue比较多,尤其vue3后,tsx,vue随意切换,我们可以有各种姿势的玩法。vue社区有一个优秀的测试库VTU,官方文档,vue2、vue3都有对应文档。里面的样例doc十分详细,提供了十分便利的api,本片只是省去样例,直接总结
测试三步走: 设定既定场景 触发既定行为 行为断言
首先基本的挂载组件来入门
- 使用
mount来渲染组件 - 使用
get() and findAll()来获取dom元素,他们都是用原生querySelector来实现的,区别就是get()方式获取的组件,如果不存在会报错 - 用
trigger() and setValue()来模拟输入 - 更新dom是一个异步操作,使用
async await来模拟dom操作
v-if v-show如何测试
- 使用
find()和exists()来判断dom元素是否存在 - 使用
get()和isVisible()来判断元素是否显示,因为v-show的元素是经过渲染在dom中真实存在的,所以
get() find()的区别
一条黄金法则: 用find()来断言元素是否存在,否则全都用get(),get当元素不存在的时候会抛出异常
另外 findAll()就是返回一组DOMWrapper的集合,同querySelectorAll
事件触发
- 通过
emitted()来获取组件中所有通过$emit或ctx.emit(如果你用了composition api)触发的事件 - 通过
emitted(eventName)来获取所有触发了指定eventName的结果,举例
//伪代码
wrapper.trigger('click')//触发click 2次
wrapper.trigger('click')// 组件内部 click会触发emit('increment')
emitted('increment').toHaveLength(2) // 就会返回一个数组 且数组长度为2,因为触发了2次
emitted('increment')[0] 就可以获取第一次触发点击事件的结果,具体结果可以根据自己的自定时间来判断,当然你不确定的时候 可以打印到控制台去查看
如何传入props和data
- 可以在挂载组件的时候通过options传入
const wrapper = mount(Component,{data:xx,props:xxx})
- 通过api来传入
await setProps({name:xxx})// 使用await来确保组件的值被正确渲染
// data可以通过获取具体dom然后setValue()来赋值
await DOM.setValue(xxx)
测试表单
测试表单是一个比较复杂的操作,因为一个复杂的表单可能包含各种各样的元素,而且数量繁多,我们先从简单的入手
常用的api就是trigger()和setValue(),就是触发时间,然后给对应表单元素赋值
- 验证提交
可以通过触发提交按钮的点击事件,或者form的submit事件来触发提交
-
验证元素内容 我们知道input是一个可替换元素,可能有各种各样的形式,不过不用担心,关于赋值我们只需操作
setValue就可以,对于OPTION RADIO CHECKBOX这种元素,如果不传参数,默认checked为true -
验证带有修饰符的事件 比如在vue中
<form @submit.prevent="handleSubmit"></form>
// form.spec.js
await form.trigger('submit.prevent') //直接复制粘贴 模板里边的事件就行
这里只是为了演示 ,需要注意的是.prevent and .stop是vue里专有的,我们可以不用测试,vue内部会有检测
再看一个更加复杂的
<input @keydown.meta.c.exact.prevent="captureCopy" v-model="input" />
//我们仍然copy里面的事件即可
await wrapper.find(input).trigger('keydown.meta.c.exact.prevent')
处理条件事件
如果你的绑定事件中需要满足一定条件之后触发,我们可以通过如下方式传入data
// .vue
handleBlur(event) {
if (event.relatedTarget.tagName === 'BUTTON') {
this.$emit('focus-lost')
}
}
// .test 我们通过此方式来传入
wrapper.find('input').trigger('blur', {
relatedTarget: componentToGetFocus
})
测试第三方库的组件
通过获取对应组件的实例 来处理
通过findComponent来获取组件实例,可以通过
- querySelector
- name
- SFC
- ref 来匹配获取对应组件实例
测试插槽
- 挂载的时候,通过第二册参数以对象的方式传入,测试只需要测试warpper的html是否包括slot的内容
const warpper = mount(comp,{
slots:{
default:'Main Content',
header:"<div>header</div>"
}
})
expect(wrapper.html()).toContain('Main Content')
expect(wrapper.html()).toContain('<div>header</div>')
- 作用于插槽
const ComponentWithSlots = {
template: `
<div class="scoped">
<slot name="scoped" v-bind="{ msg }" />
</div>
`,
data() {
return {
msg: 'world'
}
}
}
test('scoped slots', () => {
const wrapper = mount(ComponentWithSlots, {
slots: {
scoped: `<template #scoped="params">
Hello {{ params.msg }}
</template>
`
}
})
expect(wrapper.html()).toContain('Hello world')
})
记住 就是判断wrapper的innerHTML(),不用管什么插槽不插槽的
异步行为
vue里你可能通过某个行为修改了某个属性的值,dom会根据双向绑定自动更新,但这个操作vue是通过异步来执行的,你可能在下一个tick中才能获取正确的值,所以,有些操作,需要我们await nextTick()来正确通过测试
所以在VTU中,setValue(),trigger()这种异步方法值返回nextTick的,我们只需要await他们就可以了
总结
上面就是一些基本玩法,总的来说,都是获取对应的dom元素来进行断言,我们尽量不要通过组件实例上的data来进行断言,因为dom可能没有渲染到,只有dom上真正存在了我们期望的值 那才更能说明我们的操作生效了 不是吗。