e2ad1331cc
feat(测试): 新增Playwright和Vitest测试配置 feat(测试): 添加测试覆盖率报告生成功能 feat(测试): 实现前后端测试脚本集成 fix(测试): 修复测试密码不匹配问题 fix(测试): 修正URL等待策略 fix(测试): 调整错误消息选择器 refactor(测试): 重构测试目录结构 refactor(测试): 优化测试用例组织方式 docs: 更新测试报告文档 docs: 添加测试覆盖率报告模板 ci: 添加Docker测试环境配置 ci: 实现测试自动化脚本 chore: 更新依赖版本 chore: 添加测试相关配置文件
8.8 KiB
8.8 KiB
前端单元测试指南
📋 目录
测试环境配置
Vitest 配置
项目使用 Vitest 作为单元测试框架,配置文件位于 vitest.config.ts:
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
import { fileURLToPath } from 'node:url'
export default defineConfig({
plugins: [vue()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html', 'lcov'],
exclude: [
'node_modules/',
'src/test/',
'**/*.d.ts',
'**/*.config.*',
'**/mockData',
'e2e/',
],
lines: 80,
functions: 80,
branches: 80,
statements: 80,
},
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
})
测试脚本
# 运行所有单元测试
npm test
# 运行特定测试文件
npm test -- src/test/auth.test.ts
# 运行测试并生成覆盖率报告
npm run test:coverage
# 运行测试UI界面
npm run test:ui
测试工具函数
createTestHelpers
创建测试辅助函数,简化组件测试:
import { createTestHelpers } from '@/test/utils'
import { mount } from '@vue/test-utils'
import MyComponent from '@/components/MyComponent.vue'
const wrapper = mount(MyComponent)
const helpers = createTestHelpers(wrapper)
// 查找元素
const element = helpers.findByTestId('submit-button')
// 点击元素
await helpers.clickByTestId('submit-button')
// 填写表单
await helpers.fillByTestId('username', 'testuser')
waitFor
等待条件满足:
import { waitFor } from '@/test/utils'
await waitFor(() => {
return wrapper.text().includes('Success')
})
测试数据
Mock 数据
使用 src/test/fixtures.ts 中的预定义 mock 数据:
import { mockUser, mockRole, mockApiResponse } from '@/test/fixtures'
// 使用 mock 用户数据
const user = mockUser
// 使用 mock API 响应
const response = mockApiResponse(user)
编写单元测试
基础测试示例
1. 测试工具函数
import { describe, it, expect } from 'vitest'
import { formatDate } from '@/utils/date'
describe('formatDate', () => {
it('should format date correctly', () => {
const date = new Date('2024-01-01')
const result = formatDate(date)
expect(result).toBe('2024-01-01')
})
it('should handle null input', () => {
const result = formatDate(null)
expect(result).toBe('')
})
})
2. 测试 Vue 组件
import { describe, it, expect, beforeEach } from 'vitest'
import { mount } from '@vue/test-utils'
import Login from '@/views/system/Login.vue'
describe('Login Component', () => {
let wrapper: any
beforeEach(() => {
wrapper = mount(Login)
})
it('should render login form', () => {
expect(wrapper.find('input[type="text"]').exists()).toBe(true)
expect(wrapper.find('input[type="password"]').exists()).toBe(true)
expect(wrapper.find('button[type="submit"]').exists()).toBe(true)
})
it('should update username input', async () => {
const input = wrapper.find('input[type="text"]')
await input.setValue('testuser')
expect(input.element.value).toBe('testuser')
})
it('should emit login event on form submit', async () => {
const form = wrapper.find('form')
await form.trigger('submit.prevent')
expect(wrapper.emitted('login')).toBeTruthy()
})
})
3. 测试 API 客户端
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { authApi } from '@/api/auth.api'
import axios from 'axios'
vi.mock('axios')
describe('authApi', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('should login successfully', async () => {
const mockResponse = {
data: {
token: 'test-token',
user: { id: 1, username: 'testuser' }
}
}
vi.mocked(axios.post).mockResolvedValue(mockResponse)
const result = await authApi.login({ username: 'testuser', password: 'password' })
expect(result.token).toBe('test-token')
expect(result.user.username).toBe('testuser')
expect(axios.post).toHaveBeenCalledWith('/auth/login', {
username: 'testuser',
password: 'password'
})
})
})
4. 测试 Pinia Store
import { describe, it, expect, beforeEach } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { useUserStore } from '@/stores/user'
describe('User Store', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
it('should set user', () => {
const store = useUserStore()
const mockUser = { id: 1, username: 'testuser' }
store.setUser(mockUser)
expect(store.user).toEqual(mockUser)
})
it('should clear user on logout', () => {
const store = useUserStore()
store.setUser({ id: 1, username: 'testuser' })
store.logout()
expect(store.user).toBeNull()
})
})
测试最佳实践
1. 测试命名
使用清晰的测试名称,描述行为而非实现:
// ✅ 好的测试名称
it('should reject empty username')
it('should display error message when login fails')
// ❌ 不好的测试名称
it('test1')
it('test login function')
2. 测试隔离
每个测试应该独立,不依赖其他测试:
describe('User Component', () => {
let wrapper: any
beforeEach(() => {
// 每个测试前重新创建组件
wrapper = mount(UserComponent)
})
afterEach(() => {
// 每个测试后清理
wrapper.unmount()
})
})
3. 测试覆盖率
确保测试覆盖所有代码路径:
describe('validateEmail', () => {
it('should accept valid email', () => {
expect(validateEmail('test@example.com')).toBe(true)
})
it('should reject empty email', () => {
expect(validateEmail('')).toBe(false)
})
it('should reject invalid email format', () => {
expect(validateEmail('invalid')).toBe(false)
})
})
4. Mock 外部依赖
使用 mock 隔离外部依赖:
import { vi } from 'vitest'
// Mock API 调用
vi.mock('@/api/user.api', () => ({
userApi: {
getUsers: vi.fn().mockResolvedValue([])
}
}))
// Mock 浏览器 API
Object.defineProperty(window, 'localStorage', {
value: {
getItem: vi.fn(),
setItem: vi.fn(),
}
})
5. 异步测试
正确处理异步操作:
it('should handle async operation', async () => {
const wrapper = mount(Component)
// 等待异步操作完成
await wrapper.vm.$nextTick()
await waitFor(() => wrapper.text().includes('Loaded'))
expect(wrapper.text()).toContain('Loaded')
})
6. 测试用户交互
模拟用户交互:
it('should handle button click', async () => {
const wrapper = mount(Component)
const button = wrapper.find('button')
await button.trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.emitted('click')).toBeTruthy()
})
it('should handle form submission', async () => {
const wrapper = mount(Component)
const form = wrapper.find('form')
await form.trigger('submit.prevent')
await wrapper.vm.$nextTick()
expect(wrapper.emitted('submit')).toBeTruthy()
})
运行测试
运行所有测试
npm test
运行特定测试文件
npm test -- src/test/auth.test.ts
运行特定测试用例
npm test -- src/test/auth.test.ts -t "should login successfully"
生成覆盖率报告
npm run test:coverage
覆盖率报告将生成在 coverage/ 目录下。
常见问题
1. 测试超时
增加测试超时时间:
it('should handle long operation', async () => {
// 增加超时时间到 10 秒
}, 10000)
2. Mock 不生效
确保 mock 在导入模块之前:
// ❌ 错误:在导入之后 mock
import { authApi } from '@/api/auth.api'
vi.mock('@/api/auth.api')
// ✅ 正确:在导入之前 mock
vi.mock('@/api/auth.api')
import { authApi } from '@/api/auth.api'
3. 组件渲染问题
使用 shallowMount 替代 mount 减少依赖:
import { shallowMount } from '@vue/test-utils'
const wrapper = shallowMount(Component)
参考资料
最后更新时间: 2026-03-24
维护者: 张翔(全栈质量保障与研发效能工程师)