Vue单元测试
用到的库
1. vitest 是 Vitest 测试工具核心模块,提供了单元测试管理和断言等工具
2. @vue/test-utils 是 Vue.js 测试工具,辅助处理 Vue.js 在 Node.js 环境里操作 DOM 渲染和 DOM 事件等操作
3. vitejs/plugin-vue 是 vite 的插件,支持直接构建运行 Vue.js sfc代码
4. jsdom 用来在 Node.js 环境中模拟浏览器的原生 API,例如操作 DOM 的原生 API 等
安装:
npm i -D vitest @vue/test-utils @vitejs/plugin-vue @vitejs/plugin-vue jsdom
配置
创建./vitest.config.ts,写入以下内容:
import { defineConfig } from 'vitest/config';
import PluginVue from '@vitejs/plugin-vue';
export default defineConfig({
// 配置插件,用来在测试过程中编译Vue.js的模板语法
plugins: [PluginVue()],
// 配置测试环境,支持全局变量和浏览器DOM API
test: {
globals: true,
environment: 'jsdom'
}
});
./package.json
{
// ...
"scripts": {
// ...
"ui:test": "vitest",
"ui:test:update": "vitest --update",
}
// ...
}
测试覆盖率
安装:
npm i -D @vitest/coverage-v8
配置:
/vitest.config.ts
import { defineConfig } from 'vitest/config';
import PluginVue from '@vitejs/plugin-vue';
export default defineConfig({
// 配置插件,用来在测试过程中编译Vue.js的模板语法
plugins: [PluginVue()],
// 配置测试环境,支持全局变量和浏览器DOM API
test: {
globals: true,
environment: 'jsdom',
coverage: {
// 覆盖率统计工具
provider: 'v8',
// 覆盖率的分母,packages/ 目录里
include: ['packages/components/**/src/*'],
exclude: [],
// 全量覆盖率计算
all: true,
},
}
});
./package.json
{
// ...
"scripts": {
// ...
"ui:test:coverage": "vitest run --coverage",
}
// ...
}
运行npm run ui:test:coverage后根目录下会出现coverage文件夹,进去后点index.html,就会出现覆盖率分析图
| 指标 | 说明 |
|---|---|
| Statements | 语句覆盖率:是不是每个语句都执行了? |
| Branches | 分支覆盖率:是不是每个if代码块都执行了? |
| Functions | 函数覆盖率:是不是每个函数都调用了? |
| Lines | 行覆盖率:是不是每一行都执行了? |
点进具体文件就可以看到哪些代码没有覆盖到,可以针对性进行测试
常用的API介绍
@vue/test-utils
mount: 挂在组件
import { mount } from '@vue/test-utils'
test('Test visibleChange', async () => {
const wrapper = mount(Tooltip, {
props: { title: 'Tooltip Title' },
})
if (wrapper.vm.onVisibleChange) {
wrapper.vm.onVisibleChange(true)
}
await nextTick()
// 判断emit中有没有visibleChange
expect(wrapper.emitted()).toHaveProperty('visibleChange')
})
上面得到的wrapper常用的方法:
wrapper.setProps({ toastMode: true })
// 拿到这个Dom
wrapper.find('.ath-toast')
// 判断存不存在
wrapper.get('.ath-toast')
// 获得输出属性
wrapper.emitted()
// 执行行为
wrapper.trigger('click')
wrapper.trigger('mouseenter')
// 获取组件实例
wrapper.vm
// 获取HTML
wrapper.html()
vitest
如果有更新DOM的操作,必须在断言之前执行:await nextTick()
wrapper.find('button').tragger('click')
await nextTick()
含setTimeout()等计时器的代码
test('Test closeToastTooltip', async () => {
// 开启计时器环境
vi.useFakeTimers()
// 执行方法
...
// 执行及时器,不加的话含有setTimeout 之类计时器相关的代码不会被测到
vi.runAllTimers()
expect(true)
})
遇到的报错以及修改方法
1、 TypeError: window.matchMedia is not a function
解决方式:
加入以下代码,模拟window.matchMedia
import { nextTick } from 'vue'
import { mount } from '@vue/test-utils'
import { describe, test, expect, vi } from 'vitest'
import Descriptions from '../index'
describe('Descriptions', () => {
test('render', async () => {
// 解决window.matchMedia报错
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(), // Deprecated
removeListener: vi.fn(), // Deprecated
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
})
const wrapper = mount(Descriptions)
await nextTick()
expect(wrapper.html()).toMatchSnapshot()
})
})