vue单元测试总结

324 阅读3分钟

前言

单元测试的意义不再赘述,只要你看过各个框架、知名库的源码,都会涉及单元测试,这也是一个前端工程师的基本功,我们有必要学习。

起步

因为我目前用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上真正存在了我们期望的值 那才更能说明我们的操作生效了 不是吗。