前端组件测试
测试框架
| 框架 | 用途 |
|---|---|
| Vitest | 测试运行器和断言 |
| Vue Test Utils | Vue 组件挂载和交互 |
| @vitest/coverage-v8 | 覆盖率统计 |
基本结构
javascript
import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import DictSelect from '@/components/DictSelect.vue'
describe('DictSelect', () => {
it('渲染下拉选项', async () => {
const options = [
{ label: '启用', value: '1' },
{ label: '禁用', value: '0' }
]
const wrapper = mount(DictSelect, {
props: {
options,
modelValue: '1'
}
})
expect(wrapper.props('modelValue')).toBe('1')
})
it('选择选项时触发 update 事件', async () => {
const wrapper = mount(DictSelect, {
props: {
options: [{ label: '启用', value: '1' }],
modelValue: ''
}
})
await wrapper.find('[data-testid="select-trigger"]').trigger('click')
await wrapper.find('[data-testid="option-1"]').trigger('click')
expect(wrapper.emitted('update:modelValue')).toBeTruthy()
expect(wrapper.emitted('update:modelValue')[0]).toEqual(['1'])
})
})API Mock
使用 vi.mock 模拟 API 模块:
javascript
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import UserList from '@/views/system/user/index.vue'
// Mock API 模块
vi.mock('@/api/system/user', () => ({
getUserPage: vi.fn(() => Promise.resolve({
code: 200,
data: {
list: [
{ id: 1, username: 'admin', nickname: '管理员' },
{ id: 2, username: 'test', nickname: '测试' }
],
total: 2
}
}))
}))
describe('UserList', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('挂载后加载用户列表', async () => {
const wrapper = mount(UserList)
// 等待异步操作完成
await flushPromises()
// 验证表格渲染
const rows = wrapper.findAll('[data-testid="user-row"]')
expect(rows).toHaveLength(2)
})
it('搜索时传递搜索参数', async () => {
const { getUserPage } = await import('@/api/system/user')
const wrapper = mount(UserList)
await flushPromises()
// 输入搜索关键字
await wrapper.find('[data-testid="search-input"]').setValue('admin')
await wrapper.find('[data-testid="search-btn"]').trigger('click')
await flushPromises()
// 验证 API 调用参数
expect(getUserPage).toHaveBeenLastCalledWith(
expect.objectContaining({ username: 'admin' })
)
})
})Pinia Store 测试
javascript
import { describe, it, expect, beforeEach } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { useUserStore } from '@/stores/user'
describe('useUserStore', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
it('设置用户信息', () => {
const store = useUserStore()
store.setUserInfo({
id: 1,
username: 'admin',
nickname: '管理员'
})
expect(store.userId).toBe(1)
expect(store.username).toBe('admin')
})
it('清除用户信息后状态为空', () => {
const store = useUserStore()
store.setUserInfo({ id: 1, username: 'admin' })
store.clearUserInfo()
expect(store.userId).toBeNull()
expect(store.token).toBe('')
})
})测试规范
| 规范 | 说明 |
|---|---|
| 测试文件位置 | 与组件同目录的 __tests__/ 文件夹 |
| 文件命名 | 组件名.test.ts 或 组件名.spec.ts |
| data-testid | 关键元素添加 data-testid 属性便于选择 |
| 避免快照测试 | 优先使用语义化断言 |
| 异步处理 | 使用 flushPromises 等待异步完成 |
执行测试
bash
# 运行所有测试
pnpm test
# 运行指定文件
pnpm test src/components/DictSelect.test.ts
# 生成覆盖率报告
pnpm test:coverage
# 监听模式
pnpm test:watch