# 前端测试 (Vue + React) ## Vue 前端测试 ### 测试框架 - **Vitest**: 测试运行器 - **Vue Test Utils**: 组件测试 - **MSW**: API Mock ### 运行测试 ```bash npm run test npm run test:watch npm run test:coverage ``` ### 组件测试 ```typescript // UserList.test.ts import { describe, it, expect, vi } from 'vitest' import { mount } from '@vue/test-utils' import UserList from './UserList.vue' describe('UserList', () => { it('renders user list', () => { const wrapper = mount(UserList, { props: { users: [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' } ] } }) expect(wrapper.findAll('.user-item')).toHaveLength(2) expect(wrapper.text()).toContain('Alice') }) it('emits select event', async () => { const wrapper = mount(UserList, { props: { users: [{ id: 1, name: 'Alice' }] } }) await wrapper.find('.user-item').trigger('click') expect(wrapper.emitted('select')).toBeTruthy() expect(wrapper.emitted('select')[0]).toEqual([1]) }) it('shows empty state', () => { const wrapper = mount(UserList, { props: { users: [] } }) expect(wrapper.find('.empty-state').exists()).toBe(true) }) }) ``` ### API Mock (MSW) ```typescript // mocks/handlers.ts import { rest } from 'msw' export const handlers = [ rest.get('/api/v1/users', (req, res, ctx) => { return res(ctx.json({ code: 0, data: { total: 2, list: [{ id: 1, name: 'Alice' }] } })) }), rest.post('/api/v1/users', async (req, res, ctx) => { const body = await req.json() return res(ctx.json({ code: 0, data: { id: 3, ...body } })) }) ] ``` --- ## React 前端测试 ### 测试框架 - **Jest**: 测试运行器 - **React Testing Library**: 组件测试 - **Playwright**: E2E 测试 ### 运行测试 ```bash npm test npm run test:e2e npm run test:e2e:headed ``` ### 组件测试 ```typescript // TaskCard.test.tsx import { render, screen, fireEvent } from '@testing-library/react' import TaskCard from './TaskCard' describe('TaskCard', () => { const mockTask = { id: 1, title: 'Test Task', status: 'todo', priority: 'high' } it('renders task title', () => { render() expect(screen.getByText('Test Task')).toBeInTheDocument() }) it('displays priority', () => { render() expect(screen.getByText('high')).toHaveClass('priority-high') }) it('calls onClick', () => { const handleClick = jest.fn() render() fireEvent.click(screen.getByRole('article')) expect(handleClick).toHaveBeenCalledWith(mockTask) }) }) ``` ### Hook 测试 ```typescript // useTimer.test.ts import { renderHook, act } from '@testing-library/react-hooks' import { useTimer } from './useTimer' describe('useTimer', () => { beforeEach(() => jest.useFakeTimers()) afterEach(() => jest.useRealTimers()) it('starts timer', () => { const { result } = renderHook(() => useTimer()) act(() => result.current.start()) expect(result.current.isRunning).toBe(true) }) it('increments time', () => { const { result } = renderHook(() => useTimer()) act(() => { result.current.start() jest.advanceTimersByTime(3000) }) expect(result.current.seconds).toBe(3) }) }) ```