Skip to content

前端组件测试

测试框架

框架用途
Vitest测试运行器和断言
Vue Test UtilsVue 组件挂载和交互
@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

下一步

Forge Admin — 基于 Vue3 + Spring Boot 的企业级后台管理框架