Vue大型项目测试策略(单元测试、E2E测试)
大型Vue项目需建立完整的测试体系,下面梳理核心测试策略。
测试框架选型
技术栈组合
| 测试类型 | 工具 | 用途 |
|---|---|---|
| 单元测试 | Vitest | 工具函数、composables、store |
| 组件测试 | @vue/test-utils | 组件渲染与交互 |
| E2E测试 | Cypress/Playwright | 完整业务流程 |
单元测试
工具函数测试
JavaScript
// tests/unit/utils/format.test.js
import { describe, it, expect } from 'vitest'
import { formatDate, formatCurrency } from '@/utils/format'
describe('formatDate', () => {
it('should format date correctly', () => {
const date = new Date('2026-05-20')
expect(formatDate(date, 'YYYY-MM-DD')).toBe('2026-05-20')
})
it('should return empty string for invalid date', () => {
expect(formatDate(null)).toBe('')
})
})
describe('formatCurrency', () => {
it('should format number to currency', () => {
expect(formatCurrency(1000.5)).toBe('¥1,000.50')
})
})
Composables测试
JavaScript
// tests/unit/composables/useCounter.test.js
import { describe, it, expect } from 'vitest'
import { useCounter } from '@/composables/useCounter'
describe('useCounter', () => {
it('should increment counter', () => {
const { count, increment } = useCounter()
increment()
expect(count.value).toBe(1)
})
it('should decrement counter', () => {
const { count, decrement } = useCounter(10)
decrement()
expect(count.value).toBe(9)
})
})
组件测试
基础组件测试
JavaScript
// tests/components/BaseButton.spec.js
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import BaseButton from '@/components/base/BaseButton.vue'
describe('BaseButton', () => {
it('should render button with text', () => {
const wrapper = mount(BaseButton, {
slots: { default: 'Click Me' }
})
expect(wrapper.text()).toBe('Click Me')
})
it('should emit click event', async () => {
const wrapper = mount(BaseButton)
await wrapper.trigger('click')
expect(wrapper.emitted('click')).toHaveLength(1)
})
it('should be disabled when prop is true', () => {
const wrapper = mount(BaseButton, {
props: { disabled: true }
})
expect(wrapper.attributes('disabled')).toBeDefined()
})
})
Mock策略
JavaScript
// tests/components/UserCard.spec.js
import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import UserCard from '@/components/business/UserCard.vue'
import * as userApi from '@/api/user'
// Mock API
vi.mock('@/api/user', () => ({
userApi: {
getById: vi.fn().mockResolvedValue({ id: 1, name: 'Test' })
}
}))
describe('UserCard', () => {
it('should fetch user data on mount', async () => {
const wrapper = mount(UserCard, {
props: { userId: 1 }
})
await wrapper.vm.$nextTick()
expect(userApi.userApi.getById).toHaveBeenCalledWith(1)
})
})
E2E测试
Cypress示例
JavaScript
// tests/e2e/specs/login.cy.js
describe('Login Flow', () => {
beforeEach(() => {
cy.visit('/login')
})
it('should login successfully', () => {
cy.get('[data-testid="username"]').type('admin')
cy.get('[data-testid="password"]').type('password123')
cy.get('[data-testid="login-btn"]').click()
cy.url().should('include', '/dashboard')
cy.get('[data-testid="user-avatar"]').should('be.visible')
})
it('should show error for invalid credentials', () => {
cy.get('[data-testid="username"]').type('wrong')
cy.get('[data-testid="password"]').type('wrong')
cy.get('[data-testid="login-btn"]').click()
cy.get('[data-testid="error-msg"]')
.should('be.visible')
.and('contain', '用户名或密码错误')
})
})
测试覆盖率要求
覆盖率指标
JavaScript
// vitest.config.js
export default defineConfig({
test: {
coverage: {
provider: 'istanbul',
reporter: ['text', 'html', 'lcov'],
thresholds: {
branches: 70,
functions: 80,
lines: 80,
statements: 80
},
include: ['src/**/*.{js,ts,vue}'],
exclude: ['src/main.js', 'src/router/**']
}
}
})
测试金字塔
| 层级 | 占比 | 速度 | 成本 |
|---|---|---|---|
| 单元测试 | 60% | 快 | 低 |
| 组件测试 | 25% | 中 | 中 |
| E2E测试 | 15% | 慢 | 高 |
要点总结
- 测试体系采用Vitest + @vue/test-utils + Cypress组合
- 工具函数与composables优先写单元测试
- 组件测试重点验证渲染结果与用户交互
- E2E测试覆盖核心业务流程,如登录、下单
- API调用使用vi.mock隔离外部依赖
- 测试覆盖率要求行覆盖率80%+,分支覆盖率70%+
- 遵循测试金字塔,底层多顶层少
存放路径: articles/VUE/专家/大型项目架构分层设计/测试策略(单元测试、E2E测试).md
📝 发现内容有误?点击此处直接编辑