Skip to content

组件测试基础

Forge 前端测试基于 Vitest + Vue Test Utils。你需要掌握组件渲染测试和 API mock 方法。

依赖

json
{
  "devDependencies": {
    "vitest": "^2.0.0",
    "@vue/test-utils": "^2.4.0",
    "@testing-library/vue": "^8.0.0",
    "jsdom": "^24.0.0"
  }
}

测试配置

typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  test: {
    environment: 'jsdom',
    globals: true
  },
  resolve: {
    alias: {
      '@': '/src'
    }
  }
})

组件渲染测试

typescript
import { mount } from '@vue/test-utils'
import DictTag from '@/components/DictTag.vue'
import { describe, it, expect } from 'vitest'

describe('DictTag', () => {
  it('should render label correctly', async () => {
    const wrapper = mount(DictTag, {
      props: {
        value: '0',
        dictType: 'sys_user_sex'
      }
    })

    // 等待异步字典加载
    await nextTick()

    expect(wrapper.text()).toContain('男')
  })

  it('should render empty when value is null', () => {
    const wrapper = mount(DictTag, {
      props: {
        value: '',
        dictType: 'sys_user_sex'
      }
    })

    expect(wrapper.text()).toBe('')
  })
})

事件模拟

typescript
import { mount } from '@vue/test-utils'
import MyForm from '@/views/biz/order/components/MyForm.vue'

describe('MyForm', () => {
  it('should emit submit event with form data', async () => {
    const wrapper = mount(MyForm)

    // 模拟用户输入
    await wrapper.find('[data-test="orderNo"]').setValue('ORD001')
    await wrapper.find('[data-test="amount"]').setValue('100')

    // 模拟提交
    await wrapper.find('[data-test="submit"]').trigger('click')

    // 验证事件
    expect(wrapper.emitted('submit')).toBeTruthy()
    expect(wrapper.emitted('submit')[0][0]).toEqual({
      orderNo: 'ORD001',
      amount: '100'
    })
  })
})

API Mock

使用 vi.mock 模拟 API 模块:

typescript
import { vi } from 'vitest'

// Mock API 模块
vi.mock('@/api/order', () => ({
  getOrderPage: vi.fn(() =>
    Promise.resolve({
      total: 1,
      list: [{ id: 1, orderNo: 'ORD001', status: '0' }]
    })
  ),
  createOrder: vi.fn(() => Promise.resolve({ id: 1 }))
}))

import { getOrderPage, createOrder } from '@/api/order'

describe('Order Page', () => {
  it('should load order list', async () => {
    const result = await getOrderPage({ pageNum: 1, pageSize: 10 })

    expect(result.total).toBe(1)
    expect(result.list[0].orderNo).toBe('ORD001')
    expect(getOrderPage).toHaveBeenCalledWith({ pageNum: 1, pageSize: 10 })
  })

  it('should call createOrder with form data', async () => {
    const result = await createOrder({ orderNo: 'ORD002' })

    expect(createOrder).toHaveBeenCalledWith({ orderNo: 'ORD002' })
    expect(result.id).toBe(1)
  })
})

Store Mock

typescript
import { vi } from 'vitest'

vi.mock('@/stores/user', () => ({
  useUserStore: () => ({
    token: 'test-token',
    permissions: ['biz:order:list', 'biz:order:add'],
    userInfo: { nickname: '管理员' }
  })
}))

测试结构

typescript
describe('OrderList', () => {
  beforeEach(() => {
    // 每个测试前重置 mock
    vi.clearAllMocks()
  })

  it('should display orders on mount', async () => {
    // Arrange
    vi.mocked(getOrderPage).mockResolvedValue({
      total: 2,
      list: [
        { id: 1, orderNo: 'ORD001' },
        { id: 2, orderNo: 'ORD002' }
      ]
    })

    // Act
    const wrapper = mount(OrderList)
    await nextTick()

    // Assert
    expect(wrapper.findAll('[data-test="order-row"]')).toHaveLength(2)
  })
})

运行测试

bash
# 运行全部测试
pnpm test

# 运行指定文件
pnpm test src/components/DictTag.test.ts

# 监听模式
pnpm test --watch

测试原则

原则说明
只测公共行为不测内部私有方法
Mock 外部依赖API、Store 等
AAA 模式Arrange → Act → Assert
独立性测试间不依赖
描述性命名should + 期望行为

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