Vue测试策略踩坑
28.1 Vue单元测试的常见错误
核心知识点
- Vue 单元测试用于测试组件、工具函数等独立单元
- 常见错误包括:测试范围过大、未模拟依赖、断言不当等
- 正确的单元测试需要确保测试范围小、模拟依赖、断言准确
实用案例分析
错误场景:
// 错误测试:测试范围过大
// UserComponent.test.js
import { mount } from '@vue/test-utils'
import UserComponent from '@/components/UserComponent.vue'
import axios from 'axios'
// 错误:测试了网络请求,超出单元测试范围
test('should fetch user data', async () => {
const wrapper = mount(UserComponent)
await wrapper.find('button').trigger('click')
expect(wrapper.text()).toContain('User: John')
})正确实现:
// 正确测试:单元测试范围
// UserComponent.test.js
import { shallowMount } from '@vue/test-utils'
import UserComponent from '@/components/UserComponent.vue'
import axios from 'axios'
// 模拟 axios
jest.mock('axios', () => ({
get: jest.fn().mockResolvedValue({
data: { name: 'John' }
})
}))
test('should render user component', () => {
const wrapper = shallowMount(UserComponent)
expect(wrapper.exists()).toBe(true)
})
test('should call fetchUser when button is clicked', async () => {
const wrapper = shallowMount(UserComponent)
const fetchUserSpy = jest.spyOn(wrapper.vm, 'fetchUser')
await wrapper.find('button').trigger('click')
expect(fetchUserSpy).toHaveBeenCalled()
})
test('should update user data when fetchUser is called', async () => {
const wrapper = shallowMount(UserComponent)
await wrapper.vm.fetchUser()
expect(wrapper.vm.user.name).toBe('John')
})28.2 Vue组件测试的陷阱
核心知识点
- Vue 组件测试用于测试组件的渲染、交互等
- 常见陷阱包括:未模拟子组件、交互测试不当、断言不准确等
- 正确的组件测试需要确保模拟子组件、测试真实交互、断言准确
实用案例分析
错误场景:
// 错误测试:未模拟子组件
// ParentComponent.test.js
import { mount } from '@vue/test-utils'
import ParentComponent from '@/components/ParentComponent.vue'
import ChildComponent from '@/components/ChildComponent.vue' // 未模拟
test('should render parent component with child', () => {
const wrapper = mount(ParentComponent) // 会渲染真实的 ChildComponent
expect(wrapper.find('child-component').exists()).toBe(true)
})正确实现:
// 正确测试:模拟子组件
// ParentComponent.test.js
import { mount } from '@vue/test-utils'
import ParentComponent from '@/components/ParentComponent.vue'
// 模拟子组件
const mockChildComponent = {
name: 'ChildComponent',
template: '<div>Mock Child</div>'
}
test('should render parent component with child', () => {
const wrapper = mount(ParentComponent, {
components: {
ChildComponent: mockChildComponent
}
})
expect(wrapper.exists()).toBe(true)
expect(wrapper.text()).toContain('Mock Child')
})
test('should pass props to child component', () => {
const wrapper = mount(ParentComponent, {
components: {
ChildComponent: {
name: 'ChildComponent',
props: ['title'],
template: '<div>{{ title }}</div>'
}
}
})
expect(wrapper.text()).toContain('Parent Title')
})28.3 Vue集成测试的使用误区
核心知识点
- Vue 集成测试用于测试多个组件或模块的协作
- 常见误区包括:测试过于复杂、未模拟外部依赖、测试速度慢等
- 正确的集成测试需要确保测试目标明确、模拟外部依赖、保持测试速度
实用案例分析
错误场景:
// 错误测试:测试过于复杂
// App.test.js
import { mount } from '@vue/test-utils'
import App from '@/App.vue'
import router from '@/router'
import store from '@/store'
// 错误:测试了整个应用,过于复杂
test('should render full app', async () => {
const wrapper = mount(App, {
global: {
plugins: [router, store]
}
})
await router.push('/')
expect(wrapper.text()).toContain('Home Page')
await router.push('/about')
expect(wrapper.text()).toContain('About Page')
})正确实现:
// 正确测试:集成测试目标明确
// UserFlow.test.js
import { mount } from '@vue/test-utils'
import UserList from '@/components/UserList.vue'
import UserDetail from '@/components/UserDetail.vue'
import { createStore } from 'vuex'
// 创建测试 store
const createTestStore = () => {
return createStore({
state: {
users: [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }]
},
getters: {
allUsers: state => state.users
}
})
}
test('should display user list and detail', async () => {
const store = createTestStore()
const wrapper = mount(UserList, {
global: {
plugins: [store]
}
})
// 测试用户列表渲染
expect(wrapper.findAll('.user-item')).toHaveLength(2)
// 测试点击用户显示详情
await wrapper.find('.user-item').trigger('click')
const detailWrapper = mount(UserDetail, {
props: {
userId: 1
},
global: {
plugins: [store]
}
})
expect(detailWrapper.text()).toContain('User: John')
})28.4 Vue端到端测试的常见问题
核心知识点
- Vue 端到端测试用于测试完整的用户流程
- 常见问题包括:测试不稳定、速度慢、断言不当等
- 正确的端到端测试需要确保测试稳定、速度合理、断言准确
实用案例分析
错误场景:
// 错误测试:测试不稳定
// cypress/e2e/user-flow.cy.js
// 错误:使用固定等待时间,不稳定
it('should login and view dashboard', () => {
cy.visit('/login')
cy.get('input[name="username"]').type('admin')
cy.get('input[name="password"]').type('password')
cy.get('button[type="submit"]').click()
cy.wait(2000) // 错误:固定等待时间
cy.visit('/dashboard')
cy.contains('Dashboard').should('be.visible')
})正确实现:
// 正确测试:端到端测试稳定
// cypress/e2e/user-flow.cy.js
it('should login and view dashboard', () => {
cy.visit('/login')
cy.get('input[name="username"]').type('admin')
cy.get('input[name="password"]').type('password')
cy.get('button[type="submit"]').click()
// 正确:使用条件等待
cy.url().should('include', '/dashboard')
cy.contains('Dashboard').should('be.visible')
})
it('should create a new user', () => {
cy.login() // 使用自定义命令
cy.visit('/users')
cy.get('button[data-testid="add-user"]').click()
cy.get('input[name="name"]').type('New User')
cy.get('button[type="submit"]').click()
cy.contains('New User').should('be.visible')
})
// cypress/support/commands.js
Cypress.Commands.add('login', () => {
cy.visit('/login')
cy.get('input[name="username"]').type('admin')
cy.get('input[name="password"]').type('password')
cy.get('button[type="submit"]').click()
cy.url().should('include', '/dashboard')
})28.5 Vue测试覆盖率的陷阱
核心知识点
- Vue 测试覆盖率用于衡量测试的覆盖程度
- 常见陷阱包括:追求 100% 覆盖率、覆盖率数据误导、未分析覆盖率报告等
- 正确的测试覆盖率需要确保覆盖关键路径、分析覆盖率报告、关注质量而非数量
实用案例分析
错误场景:
// 错误测试:追求 100% 覆盖率
// utils.test.js
import { formatDate } from '@/utils/date'
// 错误:测试了所有分支,包括不可能的情况
test('should format date with different formats', () => {
expect(formatDate(new Date(), 'YYYY-MM-DD')).toBeTruthy()
expect(formatDate(new Date(), 'MM/DD/YYYY')).toBeTruthy()
expect(formatDate(null, 'YYYY-MM-DD')).toBe('') // 测试了 null 输入
expect(formatDate(undefined, 'YYYY-MM-DD')).toBe('') // 测试了 undefined 输入
})正确实现:
// 正确测试:关注关键路径
// utils.test.js
import { formatDate } from '@/utils/date'
test('should format date with YYYY-MM-DD format', () => {
const date = new Date('2023-01-01')
expect(formatDate(date, 'YYYY-MM-DD')).toBe('2023-01-01')
})
test('should format date with MM/DD/YYYY format', () => {
const date = new Date('2023-01-01')
expect(formatDate(date, 'MM/DD/YYYY')).toBe('01/01/2023')
})
test('should handle invalid date', () => {
expect(formatDate(null, 'YYYY-MM-DD')).toBe('')
})
// 分析覆盖率报告
// 运行:npm test -- --coverage
// 查看 coverage/lcov-report/index.html
// 关注未覆盖的关键路径28.6 Vue测试工具配置的使用误区
核心知识点
- Vue 测试工具配置包括 Jest、Cypress、Vue Test Utils 等
- 常见误区包括:配置错误、版本不兼容、插件冲突等
- 正确的配置需要确保版本兼容、配置正确、避免冲突
实用案例分析
错误场景:
// 错误配置:测试工具版本不兼容
// package.json
{
"devDependencies": {
"jest": "^29.0.0",
"@vue/test-utils": "^1.3.0", // 错误:@vue/test-utils 1.x 不兼容 Vue 3
"vue-jest": "^4.0.0" // 错误:vue-jest 4.x 不兼容 Jest 29
}
}正确实现:
// 正确配置:测试工具版本兼容
// package.json (Vue 2)
{
"devDependencies": {
"jest": "^26.0.0",
"@vue/test-utils": "^1.3.0",
"vue-jest": "^3.0.7",
"babel-jest": "^26.0.0"
}
}
// package.json (Vue 3)
{
"devDependencies": {
"jest": "^29.0.0",
"@vue/test-utils": "^2.0.0",
"@vue/vue3-jest": "^29.0.0",
"babel-jest": "^29.0.0"
}
}
// jest.config.js
module.exports = {
moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
transform: {
'^.+\\.vue$': '@vue/vue3-jest',
'^.+\\.js$': 'babel-jest'
},
testMatch: ['**/*.test.js'],
collectCoverageFrom: ['src/**/*.{js,vue}'],
coverageDirectory: '<rootDir>/coverage'
}28.7 Vue模拟数据的常见错误
核心知识点
- Vue 模拟数据用于测试中替代真实数据
- 常见错误包括:模拟数据不真实、未覆盖边界情况、模拟过于复杂等
- 正确的模拟数据需要确保数据真实、覆盖边界情况、模拟简洁
实用案例分析
错误场景:
// 错误模拟:模拟数据不真实
// userService.test.js
import userService from '@/services/userService'
import axios from 'axios'
jest.mock('axios', () => ({
get: jest.fn().mockResolvedValue({
data: {} // 错误:空对象,不真实
})
}))
test('should fetch user', async () => {
const user = await userService.fetchUser(1)
expect(user).toBeDefined()
})正确实现:
// 正确模拟:模拟数据真实
// userService.test.js
import userService from '@/services/userService'
import axios from 'axios'
// 真实的模拟数据
const mockUser = {
id: 1,
name: 'John Doe',
email: 'john@example.com',
role: 'user'
}
jest.mock('axios', () => ({
get: jest.fn().mockResolvedValue({
data: mockUser
})
}))
test('should fetch user', async () => {
const user = await userService.fetchUser(1)
expect(user).toEqual(mockUser)
expect(user.id).toBe(1)
expect(user.name).toBe('John Doe')
})
// 测试边界情况
test('should handle 404 error', async () => {
axios.get.mockRejectedValue({
response: {
status: 404
}
})
await expect(userService.fetchUser(999)).rejects.toThrow('User not found')
})28.8 Vue测试环境的陷阱
核心知识点
- Vue 测试环境包括测试数据库、环境变量、浏览器环境等
- 常见陷阱包括:环境配置错误、数据库未隔离、环境变量冲突等
- 正确的测试环境需要确保配置正确、环境隔离、变量合理
实用案例分析
错误场景:
// 错误环境:测试环境配置错误
// .env.test
NODE_ENV=production // 错误:测试环境应为 test
VUE_APP_API_BASE_URL=https://api.example.com // 错误:应使用测试 API
// 测试数据库未隔离
// 使用了开发数据库,可能影响开发数据正确实现:
// 正确环境:测试环境配置
// .env.test
NODE_ENV=test
VUE_APP_API_BASE_URL=http://localhost:3000/api // 测试 API
VUE_APP_DB_NAME=test_db // 测试数据库
// jest.config.js 中加载环境变量
require('dotenv').config({ path: '.env.test' })
// 测试数据库隔离
// 使用内存数据库或测试专用数据库
// userService.test.js
import userService from '@/services/userService'
import { setupTestDB, teardownTestDB } from '@/tests/utils'
beforeEach(async () => {
await setupTestDB() // 重置测试数据库
})
afterEach(async () => {
await teardownTestDB() // 清理测试数据库
})
test('should create user', async () => {
const user = await userService.createUser({ name: 'Test User' })
expect(user.id).toBeDefined()
expect(user.name).toBe('Test User')
})