vitest 是由 vite 提供支持的极速单元测试框架,VueTestUtils 是 Vue.js 的官方测试实用程序库,Vue Router 是 Vue.js 的官方路由,以上均为各自官网对其的描述
项目中使用路由是十分常见,所以对它来个单元测试也是十分必要的
// app.vue
<template>
<nav>
<RouterLink to="/">home</RouterLink>
<RouterLink data-test="about" to="/about">about</RouterLink>
</nav>
<RouterView />
</template>
// home.vue
<template>
<div>主页</div>
</template>
// about.vue
<template>
<div>关于</div>
</template>
// spec | test
import { describe, it, expect } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import { createRouter, createWebHistory } from 'vue-router'
import App from '@/App.vue'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
describe('router-test', () => {
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About },
],
})
it('test1', async () => {
router.push('/')
await router.isReady()
const wrapper = mount(App, {
global: { plugins: [router] },
})
expect(wrapper.findComponent(Home).exists()).toBe(true)
})
it('test2', async () => {
router.push('/')
await router.isReady()
const wrapper = mount(App, {
global: { plugins: [router] },
})
expect(wrapper.html()).toContain('主页')
await wrapper.find('[data-test="about"]').trigger('click')
await flushPromises()
expect(wrapper.html()).toContain('关于')
})
})
- 此 demo 由组件部分和测试文件两个部分组成
- app 组件里是一个基础的单页应用,点击对应的 route-link,route-view 中展示对应组件
- 测试文件因为是在单元测试环境下,所以我们需要先安装 VueRouter,VueRouter 是一个插件,在挂载组件时使用安装选项安装
global: {plugins: []}
第一组测试首先执行了跳转,因为 VueRouter4 异步处理路由,需要告诉路由器何时准备就绪,所以使用了
await router.isReady()
组件通过 test-utils 库的查找组件的方法
findComponet
查找 Home,再通过exists()
验证元素是否存在
然后使用 vitest 库的 expect 创建断言,toBe 断言基础对象是否相等第二组测试初始部分与第一组相同
组件通过 test-utils 库 html 方法返回元素的 HTML
然后使用 vitest 库的 expect 创建断言,toContain 断言检查值是否在数组中
再通过 find 方法返回查找元素,trigger 方法触发 DOM 事件,模拟用户点击的操作,同样是因为 VueRouter 的异步性质,我们需要再断言之前完成路由,由于没有可以等待的hasNavigated
钩子,所以用 test-utils 提供的flushPromises
替代,该方法会刷新所有已解决的 promise 程序,点击前后都断言了,会判断实际执行结果与预期结果是否一致,如果不一致则会抛出错误
同样的这是一个简单的 demo,但它还是有很多值得注意的知识点
router.push('xx')
这样子,针对它的单元测试可以这样写it('编程式导航', async () => {
const wrapper = mount(App, {
global: { plugins: [router] },
})
// vi.spyOn 在对象的方法或 getter/setter 上创建一个监听
const spy = vi.spyOn(router, 'push') // 监听router的push方法
// 模拟用户按下按钮
await wrapper.find('button').trigger('click')
// toHaveBeenCalledTimes 断言检查一个函数是否被调用了一定的次数
expect(spy).toHaveBeenCalledTimes(1)
// toHaveBeenCalledWith 断言检查一个函数是否被调用过,并且传入了指定的参数
expect(spy).toHaveBeenCalledWith('/about')
// 后面就是和上面的测试代码一样的逻辑了
})
vitest 单元测试配合@vue/test-utils 之组件单元测试篇
vitest 单元测试配合@vue/test-utils 之 pinia 篇