组件测试基础
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 + 期望行为 |
