测试指南 
本指南介绍如何在 Trae 应用程序中实施全面的测试策略,包括单元测试、集成测试、端到端测试等。
概述 
测试是确保应用程序质量和可靠性的关键环节。Trae 支持多种测试框架和工具,帮助您构建健壮的测试套件。本指南将涵盖测试策略、工具选择、最佳实践等方面。
测试策略 
测试金字塔 
    /\     E2E Tests (少量)
   /  \    
  /____\   Integration Tests (适量)
 /______\  Unit Tests (大量)- 单元测试 (70%):测试单个函数或组件
- 集成测试 (20%):测试组件间的交互
- 端到端测试 (10%):测试完整的用户流程
测试配置 
javascript
// jest.config.js
module.exports = {
  // 测试环境
  testEnvironment: 'jsdom',
  
  // 设置文件
  setupFilesAfterEnv: ['<rootDir>/src/test/setup.js'],
  
  // 模块路径映射
  moduleNameMapping: {
    '^@/(.*)$': '<rootDir>/src/$1',
    '^@components/(.*)$': '<rootDir>/src/components/$1',
    '^@utils/(.*)$': '<rootDir>/src/utils/$1',
    '^@services/(.*)$': '<rootDir>/src/services/$1'
  },
  
  // 测试文件匹配模式
  testMatch: [
    '<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}',
    '<rootDir>/src/**/*.{test,spec}.{js,jsx,ts,tsx}'
  ],
  
  // 覆盖率配置
  collectCoverageFrom: [
    'src/**/*.{js,jsx,ts,tsx}',
    '!src/**/*.d.ts',
    '!src/index.js',
    '!src/serviceWorker.js',
    '!src/**/*.stories.{js,jsx,ts,tsx}'
  ],
  
  // 覆盖率阈值
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  },
  
  // 转换配置
  transform: {
    '^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
    '^.+\\.css$': 'jest-transform-css',
    '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': 'jest-transform-file'
  },
  
  // 模拟文件
  moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json'],
  
  // 忽略的转换路径
  transformIgnorePatterns: [
    '[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$',
    '^.+\\.module\\.(css|sass|scss)$'
  ],
  
  // 模拟模块
  moduleNameMapping: {
    '\\.(css|less|scss|sass)$': 'identity-obj-proxy'
  }
};测试设置 
javascript
// src/test/setup.js
import '@testing-library/jest-dom';
import { configure } from '@testing-library/react';
import { server } from './mocks/server';
// 配置 Testing Library
configure({ testIdAttribute: 'data-testid' });
// 设置 MSW
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
// 全局模拟
global.ResizeObserver = jest.fn().mockImplementation(() => ({
  observe: jest.fn(),
  unobserve: jest.fn(),
  disconnect: jest.fn()
}));
global.IntersectionObserver = jest.fn().mockImplementation(() => ({
  observe: jest.fn(),
  unobserve: jest.fn(),
  disconnect: jest.fn()
}));
// 模拟 localStorage
const localStorageMock = {
  getItem: jest.fn(),
  setItem: jest.fn(),
  removeItem: jest.fn(),
  clear: jest.fn()
};
global.localStorage = localStorageMock;
// 模拟 fetch
global.fetch = jest.fn();
// 清理函数
afterEach(() => {
  jest.clearAllMocks();
  localStorageMock.clear();
});单元测试 
组件测试 
javascript
// src/components/Button/__tests__/Button.test.jsx
import { render, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Button from '../Button';
describe('Button Component', () => {
  it('renders with correct text', () => {
    render(<Button>Click me</Button>);
    expect(screen.getByRole('button', { name: /click me/i })).toBeInTheDocument();
  });
  
  it('handles click events', async () => {
    const user = userEvent.setup();
    const handleClick = jest.fn();
    
    render(<Button onClick={handleClick}>Click me</Button>);
    
    await user.click(screen.getByRole('button'));
    expect(handleClick).toHaveBeenCalledTimes(1);
  });
  
  it('applies correct variant styles', () => {
    const { rerender } = render(<Button variant="primary">Primary</Button>);
    expect(screen.getByRole('button')).toHaveClass('btn-primary');
    
    rerender(<Button variant="secondary">Secondary</Button>);
    expect(screen.getByRole('button')).toHaveClass('btn-secondary');
  });
  
  it('disables button when loading', () => {
    render(<Button loading>Loading</Button>);
    expect(screen.getByRole('button')).toBeDisabled();
    expect(screen.getByText(/loading/i)).toBeInTheDocument();
  });
  
  it('renders with custom className', () => {
    render(<Button className="custom-class">Button</Button>);
    expect(screen.getByRole('button')).toHaveClass('custom-class');
  });
  
  it('forwards ref correctly', () => {
    const ref = React.createRef();
    render(<Button ref={ref}>Button</Button>);
    expect(ref.current).toBeInstanceOf(HTMLButtonElement);
  });
});Hook 测试 
javascript
// src/hooks/__tests__/useCounter.test.js
import { renderHook, act } from '@testing-library/react';
import useCounter from '../useCounter';
describe('useCounter Hook', () => {
  it('initializes with default value', () => {
    const { result } = renderHook(() => useCounter());
    expect(result.current.count).toBe(0);
  });
  
  it('initializes with custom value', () => {
    const { result } = renderHook(() => useCounter(10));
    expect(result.current.count).toBe(10);
  });
  
  it('increments count', () => {
    const { result } = renderHook(() => useCounter());
    
    act(() => {
      result.current.increment();
    });
    
    expect(result.current.count).toBe(1);
  });
  
  it('decrements count', () => {
    const { result } = renderHook(() => useCounter(5));
    
    act(() => {
      result.current.decrement();
    });
    
    expect(result.current.count).toBe(4);
  });
  
  it('resets count', () => {
    const { result } = renderHook(() => useCounter(10));
    
    act(() => {
      result.current.increment();
      result.current.reset();
    });
    
    expect(result.current.count).toBe(10);
  });
  
  it('sets count to specific value', () => {
    const { result } = renderHook(() => useCounter());
    
    act(() => {
      result.current.setValue(42);
    });
    
    expect(result.current.count).toBe(42);
  });
});工具函数测试 
javascript
// src/utils/__tests__/formatters.test.js
import {
  formatCurrency,
  formatDate,
  formatFileSize,
  truncateText
} from '../formatters';
describe('Formatter Utils', () => {
  describe('formatCurrency', () => {
    it('formats positive numbers correctly', () => {
      expect(formatCurrency(1234.56)).toBe('$1,234.56');
      expect(formatCurrency(0)).toBe('$0.00');
      expect(formatCurrency(999999.99)).toBe('$999,999.99');
    });
    
    it('formats negative numbers correctly', () => {
      expect(formatCurrency(-1234.56)).toBe('-$1,234.56');
    });
    
    it('handles different currencies', () => {
      expect(formatCurrency(1234.56, 'EUR')).toBe('€1,234.56');
      expect(formatCurrency(1234.56, 'JPY')).toBe('¥1,235');
    });
    
    it('throws error for invalid input', () => {
      expect(() => formatCurrency('invalid')).toThrow();
      expect(() => formatCurrency(null)).toThrow();
    });
  });
  
  describe('formatDate', () => {
    it('formats dates correctly', () => {
      const date = new Date('2023-12-25T10:30:00Z');
      expect(formatDate(date)).toBe('2023-12-25');
      expect(formatDate(date, 'MM/dd/yyyy')).toBe('12/25/2023');
    });
    
    it('handles string dates', () => {
      expect(formatDate('2023-12-25')).toBe('2023-12-25');
    });
    
    it('throws error for invalid dates', () => {
      expect(() => formatDate('invalid-date')).toThrow();
    });
  });
  
  describe('formatFileSize', () => {
    it('formats bytes correctly', () => {
      expect(formatFileSize(0)).toBe('0 B');
      expect(formatFileSize(1024)).toBe('1 KB');
      expect(formatFileSize(1048576)).toBe('1 MB');
      expect(formatFileSize(1073741824)).toBe('1 GB');
    });
    
    it('handles decimal places', () => {
      expect(formatFileSize(1536, 1)).toBe('1.5 KB');
      expect(formatFileSize(1536, 2)).toBe('1.50 KB');
    });
  });
  
  describe('truncateText', () => {
    it('truncates long text', () => {
      const longText = 'This is a very long text that should be truncated';
      expect(truncateText(longText, 20)).toBe('This is a very long...');
    });
    
    it('returns original text if shorter than limit', () => {
      const shortText = 'Short text';
      expect(truncateText(shortText, 20)).toBe('Short text');
    });
    
    it('handles custom suffix', () => {
      const text = 'This is a long text';
      expect(truncateText(text, 10, ' [more]')).toBe('This is a [more]');
    });
  });
});集成测试 
API 集成测试 
javascript
// src/services/__tests__/userService.test.js
import { rest } from 'msw';
import { server } from '../../test/mocks/server';
import userService from '../userService';
describe('User Service', () => {
  describe('getUsers', () => {
    it('fetches users successfully', async () => {
      const mockUsers = [
        { id: 1, name: 'John Doe', email: 'john@example.com' },
        { id: 2, name: 'Jane Smith', email: 'jane@example.com' }
      ];
      
      server.use(
        rest.get('/api/users', (req, res, ctx) => {
          return res(ctx.json(mockUsers));
        })
      );
      
      const users = await userService.getUsers();
      expect(users).toEqual(mockUsers);
    });
    
    it('handles API errors', async () => {
      server.use(
        rest.get('/api/users', (req, res, ctx) => {
          return res(ctx.status(500), ctx.json({ error: 'Internal Server Error' }));
        })
      );
      
      await expect(userService.getUsers()).rejects.toThrow('Failed to fetch users');
    });
    
    it('handles network errors', async () => {
      server.use(
        rest.get('/api/users', (req, res, ctx) => {
          return res.networkError('Network error');
        })
      );
      
      await expect(userService.getUsers()).rejects.toThrow('Network error');
    });
  });
  
  describe('createUser', () => {
    it('creates user successfully', async () => {
      const newUser = { name: 'New User', email: 'new@example.com' };
      const createdUser = { id: 3, ...newUser };
      
      server.use(
        rest.post('/api/users', async (req, res, ctx) => {
          const body = await req.json();
          expect(body).toEqual(newUser);
          return res(ctx.status(201), ctx.json(createdUser));
        })
      );
      
      const result = await userService.createUser(newUser);
      expect(result).toEqual(createdUser);
    });
    
    it('handles validation errors', async () => {
      server.use(
        rest.post('/api/users', (req, res, ctx) => {
          return res(
            ctx.status(400),
            ctx.json({
              error: 'Validation failed',
              details: { email: 'Email is required' }
            })
          );
        })
      );
      
      await expect(userService.createUser({})).rejects.toThrow('Validation failed');
    });
  });
});组件集成测试 
javascript
// src/components/UserList/__tests__/UserList.integration.test.jsx
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { rest } from 'msw';
import { server } from '../../../test/mocks/server';
import UserList from '../UserList';
import { AuthProvider } from '../../../contexts/AuthContext';
import { QueryClient, QueryClientProvider } from 'react-query';
const renderWithProviders = (component) => {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: { retry: false },
      mutations: { retry: false }
    }
  });
  
  return render(
    <QueryClientProvider client={queryClient}>
      <AuthProvider>
        {component}
      </AuthProvider>
    </QueryClientProvider>
  );
};
describe('UserList Integration', () => {
  const mockUsers = [
    { id: 1, name: 'John Doe', email: 'john@example.com', role: 'admin' },
    { id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'user' }
  ];
  
  beforeEach(() => {
    server.use(
      rest.get('/api/users', (req, res, ctx) => {
        return res(ctx.json(mockUsers));
      })
    );
  });
  
  it('loads and displays users', async () => {
    renderWithProviders(<UserList />);
    
    expect(screen.getByText(/loading/i)).toBeInTheDocument();
    
    await waitFor(() => {
      expect(screen.getByText('John Doe')).toBeInTheDocument();
      expect(screen.getByText('Jane Smith')).toBeInTheDocument();
    });
  });
  
  it('handles search functionality', async () => {
    const user = userEvent.setup();
    renderWithProviders(<UserList />);
    
    await waitFor(() => {
      expect(screen.getByText('John Doe')).toBeInTheDocument();
    });
    
    const searchInput = screen.getByPlaceholderText(/search users/i);
    await user.type(searchInput, 'John');
    
    expect(screen.getByText('John Doe')).toBeInTheDocument();
    expect(screen.queryByText('Jane Smith')).not.toBeInTheDocument();
  });
  
  it('handles user deletion', async () => {
    const user = userEvent.setup();
    
    server.use(
      rest.delete('/api/users/:id', (req, res, ctx) => {
        return res(ctx.status(204));
      }),
      rest.get('/api/users', (req, res, ctx) => {
        return res(ctx.json(mockUsers.filter(u => u.id !== 1)));
      })
    );
    
    renderWithProviders(<UserList />);
    
    await waitFor(() => {
      expect(screen.getByText('John Doe')).toBeInTheDocument();
    });
    
    const deleteButton = screen.getAllByText(/delete/i)[0];
    await user.click(deleteButton);
    
    const confirmButton = screen.getByText(/confirm/i);
    await user.click(confirmButton);
    
    await waitFor(() => {
      expect(screen.queryByText('John Doe')).not.toBeInTheDocument();
      expect(screen.getByText('Jane Smith')).toBeInTheDocument();
    });
  });
  
  it('handles API errors gracefully', async () => {
    server.use(
      rest.get('/api/users', (req, res, ctx) => {
        return res(ctx.status(500), ctx.json({ error: 'Server error' }));
      })
    );
    
    renderWithProviders(<UserList />);
    
    await waitFor(() => {
      expect(screen.getByText(/error loading users/i)).toBeInTheDocument();
    });
  });
});端到端测试 
Playwright 配置 
javascript
// playwright.config.js
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
  testDir: './e2e',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',
  
  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure'
  },
  
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] }
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] }
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] }
    },
    {
      name: 'Mobile Chrome',
      use: { ...devices['Pixel 5'] }
    },
    {
      name: 'Mobile Safari',
      use: { ...devices['iPhone 12'] }
    }
  ],
  
  webServer: {
    command: 'npm run start',
    port: 3000,
    reuseExistingServer: !process.env.CI
  }
});E2E 测试示例 
javascript
// e2e/auth.spec.js
import { test, expect } from '@playwright/test';
test.describe('Authentication Flow', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/');
  });
  
  test('user can login successfully', async ({ page }) => {
    // 导航到登录页面
    await page.click('text=Login');
    await expect(page).toHaveURL('/login');
    
    // 填写登录表单
    await page.fill('[data-testid="email-input"]', 'test@example.com');
    await page.fill('[data-testid="password-input"]', 'password123');
    
    // 提交表单
    await page.click('[data-testid="login-button"]');
    
    // 验证登录成功
    await expect(page).toHaveURL('/dashboard');
    await expect(page.locator('[data-testid="user-menu"]')).toBeVisible();
    await expect(page.locator('text=Welcome back')).toBeVisible();
  });
  
  test('shows error for invalid credentials', async ({ page }) => {
    await page.click('text=Login');
    
    await page.fill('[data-testid="email-input"]', 'invalid@example.com');
    await page.fill('[data-testid="password-input"]', 'wrongpassword');
    
    await page.click('[data-testid="login-button"]');
    
    await expect(page.locator('[data-testid="error-message"]')).toBeVisible();
    await expect(page.locator('text=Invalid credentials')).toBeVisible();
  });
  
  test('user can logout', async ({ page }) => {
    // 先登录
    await page.goto('/login');
    await page.fill('[data-testid="email-input"]', 'test@example.com');
    await page.fill('[data-testid="password-input"]', 'password123');
    await page.click('[data-testid="login-button"]');
    
    await expect(page).toHaveURL('/dashboard');
    
    // 登出
    await page.click('[data-testid="user-menu"]');
    await page.click('text=Logout');
    
    // 验证登出成功
    await expect(page).toHaveURL('/');
    await expect(page.locator('text=Login')).toBeVisible();
  });
  
  test('redirects to login when accessing protected route', async ({ page }) => {
    await page.goto('/dashboard');
    await expect(page).toHaveURL('/login');
  });
});复杂用户流程测试 
javascript
// e2e/project-management.spec.js
import { test, expect } from '@playwright/test';
test.describe('Project Management', () => {
  test.beforeEach(async ({ page }) => {
    // 登录
    await page.goto('/login');
    await page.fill('[data-testid="email-input"]', 'admin@example.com');
    await page.fill('[data-testid="password-input"]', 'admin123');
    await page.click('[data-testid="login-button"]');
    await expect(page).toHaveURL('/dashboard');
  });
  
  test('complete project creation flow', async ({ page }) => {
    // 导航到项目页面
    await page.click('text=Projects');
    await expect(page).toHaveURL('/projects');
    
    // 创建新项目
    await page.click('[data-testid="create-project-button"]');
    
    // 填写项目信息
    await page.fill('[data-testid="project-name"]', 'Test Project');
    await page.fill('[data-testid="project-description"]', 'This is a test project');
    await page.selectOption('[data-testid="project-template"]', 'react');
    
    // 配置项目设置
    await page.check('[data-testid="enable-typescript"]');
    await page.check('[data-testid="enable-testing"]');
    
    // 提交创建
    await page.click('[data-testid="create-button"]');
    
    // 等待项目创建完成
    await expect(page.locator('[data-testid="project-status"]')).toHaveText('Ready', { timeout: 30000 });
    
    // 验证项目详情
    await expect(page.locator('[data-testid="project-title"]')).toHaveText('Test Project');
    await expect(page.locator('[data-testid="project-description"]')).toHaveText('This is a test project');
    
    // 验证文件结构
    await expect(page.locator('[data-testid="file-tree"]')).toBeVisible();
    await expect(page.locator('text=src/')).toBeVisible();
    await expect(page.locator('text=package.json')).toBeVisible();
    await expect(page.locator('text=tsconfig.json')).toBeVisible();
  });
  
  test('file editing and saving', async ({ page }) => {
    // 打开现有项目
    await page.goto('/projects/test-project');
    
    // 打开文件
    await page.click('text=src/');
    await page.click('text=App.tsx');
    
    // 等待编辑器加载
    await expect(page.locator('[data-testid="code-editor"]')).toBeVisible();
    
    // 编辑代码
    await page.click('[data-testid="code-editor"]');
    await page.keyboard.press('Control+A');
    await page.keyboard.type(`
function App() {
  return (
    <div className="App">
      <h1>Hello, Trae!</h1>
    </div>
  );
}
export default App;
`);
    
    // 保存文件
    await page.keyboard.press('Control+S');
    
    // 验证保存状态
    await expect(page.locator('[data-testid="save-indicator"]')).toHaveText('Saved');
    
    // 运行项目
    await page.click('[data-testid="run-button"]');
    
    // 等待预览加载
    await expect(page.locator('[data-testid="preview-frame"]')).toBeVisible({ timeout: 10000 });
    
    // 验证预览内容
    const previewFrame = page.frameLocator('[data-testid="preview-frame"]');
    await expect(previewFrame.locator('h1')).toHaveText('Hello, Trae!');
  });
  
  test('collaboration features', async ({ page, context }) => {
    // 打开项目
    await page.goto('/projects/shared-project');
    
    // 邀请协作者
    await page.click('[data-testid="share-button"]');
    await page.fill('[data-testid="invite-email"]', 'collaborator@example.com');
    await page.selectOption('[data-testid="permission-level"]', 'editor');
    await page.click('[data-testid="send-invite"]');
    
    // 验证邀请发送
    await expect(page.locator('text=Invitation sent')).toBeVisible();
    
    // 模拟协作者加入
    const collaboratorPage = await context.newPage();
    await collaboratorPage.goto('/login');
    await collaboratorPage.fill('[data-testid="email-input"]', 'collaborator@example.com');
    await collaboratorPage.fill('[data-testid="password-input"]', 'password123');
    await collaboratorPage.click('[data-testid="login-button"]');
    
    await collaboratorPage.goto('/projects/shared-project');
    
    // 验证实时协作
    await page.click('text=src/App.tsx');
    await collaboratorPage.click('text=src/App.tsx');
    
    // 在主页面编辑
    await page.click('[data-testid="code-editor"]');
    await page.keyboard.type('// Main user edit\n');
    
    // 验证协作者页面显示更改
    await expect(collaboratorPage.locator('text=// Main user edit')).toBeVisible({ timeout: 5000 });
    
    // 在协作者页面编辑
    await collaboratorPage.click('[data-testid="code-editor"]');
    await collaboratorPage.keyboard.type('// Collaborator edit\n');
    
    // 验证主页面显示更改
    await expect(page.locator('text=// Collaborator edit')).toBeVisible({ timeout: 5000 });
  });
});测试工具和辅助函数 
测试工具库 
javascript
// src/test/utils/testUtils.jsx
import { render } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from 'react-query';
import { BrowserRouter } from 'react-router-dom';
import { AuthProvider } from '../../contexts/AuthContext';
import { ThemeProvider } from '../../contexts/ThemeContext';
// 创建测试查询客户端
const createTestQueryClient = () => new QueryClient({
  defaultOptions: {
    queries: {
      retry: false,
      cacheTime: 0
    },
    mutations: {
      retry: false
    }
  }
});
// 全功能渲染函数
export function renderWithProviders(
  ui,
  {
    initialEntries = ['/'],
    user = null,
    queryClient = createTestQueryClient(),
    ...renderOptions
  } = {}
) {
  function Wrapper({ children }) {
    return (
      <QueryClientProvider client={queryClient}>
        <BrowserRouter>
          <AuthProvider initialUser={user}>
            <ThemeProvider>
              {children}
            </ThemeProvider>
          </AuthProvider>
        </BrowserRouter>
      </QueryClientProvider>
    );
  }
  
  return {
    ...render(ui, { wrapper: Wrapper, ...renderOptions }),
    queryClient
  };
}
// 模拟用户数据
export const mockUser = {
  id: 1,
  name: 'Test User',
  email: 'test@example.com',
  roles: ['user'],
  permissions: ['read:projects']
};
export const mockAdminUser = {
  id: 2,
  name: 'Admin User',
  email: 'admin@example.com',
  roles: ['admin'],
  permissions: ['read:projects', 'create:projects', 'update:projects', 'delete:projects']
};
// 等待异步操作完成
export const waitForLoadingToFinish = () => {
  return waitFor(() => {
    expect(screen.queryByText(/loading/i)).not.toBeInTheDocument();
  });
};
// 模拟文件上传
export const createMockFile = (name = 'test.txt', size = 1024, type = 'text/plain') => {
  const file = new File(['test content'], name, { type });
  Object.defineProperty(file, 'size', { value: size });
  return file;
};
// 模拟拖拽事件
export const createDragEvent = (type, files = []) => {
  const event = new Event(type, { bubbles: true });
  Object.defineProperty(event, 'dataTransfer', {
    value: {
      files,
      types: ['Files']
    }
  });
  return event;
};
// 表单测试辅助函数
export const fillForm = async (fields) => {
  const user = userEvent.setup();
  
  for (const [fieldName, value] of Object.entries(fields)) {
    const field = screen.getByLabelText(new RegExp(fieldName, 'i'));
    
    if (field.type === 'checkbox' || field.type === 'radio') {
      if (value) {
        await user.click(field);
      }
    } else if (field.tagName === 'SELECT') {
      await user.selectOptions(field, value);
    } else {
      await user.clear(field);
      await user.type(field, value);
    }
  }
};
// 等待元素出现
export const waitForElement = (selector, options = {}) => {
  return waitFor(() => {
    const element = screen.getByTestId(selector);
    expect(element).toBeInTheDocument();
    return element;
  }, options);
};Mock 服务 
javascript
// src/test/mocks/handlers.js
import { rest } from 'msw';
const baseURL = process.env.REACT_APP_API_URL || 'http://localhost:3001';
export const handlers = [
  // 认证相关
  rest.post(`${baseURL}/auth/login`, (req, res, ctx) => {
    const { email, password } = req.body;
    
    if (email === 'test@example.com' && password === 'password123') {
      return res(
        ctx.json({
          user: {
            id: 1,
            name: 'Test User',
            email: 'test@example.com',
            roles: ['user']
          },
          accessToken: 'mock-access-token',
          refreshToken: 'mock-refresh-token'
        })
      );
    }
    
    return res(
      ctx.status(401),
      ctx.json({ error: 'Invalid credentials' })
    );
  }),
  
  rest.post(`${baseURL}/auth/refresh`, (req, res, ctx) => {
    return res(
      ctx.json({
        accessToken: 'new-mock-access-token'
      })
    );
  }),
  
  rest.post(`${baseURL}/auth/logout`, (req, res, ctx) => {
    return res(ctx.status(204));
  }),
  
  // 用户管理
  rest.get(`${baseURL}/users`, (req, res, ctx) => {
    return res(
      ctx.json([
        { id: 1, name: 'John Doe', email: 'john@example.com', role: 'admin' },
        { id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'user' }
      ])
    );
  }),
  
  rest.post(`${baseURL}/users`, (req, res, ctx) => {
    const user = req.body;
    return res(
      ctx.status(201),
      ctx.json({ id: Date.now(), ...user })
    );
  }),
  
  rest.put(`${baseURL}/users/:id`, (req, res, ctx) => {
    const { id } = req.params;
    const updates = req.body;
    return res(
      ctx.json({ id: parseInt(id), ...updates })
    );
  }),
  
  rest.delete(`${baseURL}/users/:id`, (req, res, ctx) => {
    return res(ctx.status(204));
  }),
  
  // 项目管理
  rest.get(`${baseURL}/projects`, (req, res, ctx) => {
    return res(
      ctx.json([
        {
          id: 1,
          name: 'Test Project',
          description: 'A test project',
          status: 'active',
          createdAt: '2023-01-01T00:00:00Z'
        }
      ])
    );
  }),
  
  rest.post(`${baseURL}/projects`, (req, res, ctx) => {
    const project = req.body;
    return res(
      ctx.status(201),
      ctx.json({
        id: Date.now(),
        ...project,
        status: 'creating',
        createdAt: new Date().toISOString()
      })
    );
  }),
  
  // 文件操作
  rest.get(`${baseURL}/projects/:id/files`, (req, res, ctx) => {
    return res(
      ctx.json({
        files: [
          { name: 'src/', type: 'directory' },
          { name: 'package.json', type: 'file' },
          { name: 'README.md', type: 'file' }
        ]
      })
    );
  }),
  
  rest.get(`${baseURL}/projects/:id/files/*`, (req, res, ctx) => {
    return res(
      ctx.text('// File content')
    );
  }),
  
  rest.put(`${baseURL}/projects/:id/files/*`, (req, res, ctx) => {
    return res(ctx.status(204));
  })
];性能测试 
组件性能测试 
javascript
// src/components/__tests__/performance.test.jsx
import { render } from '@testing-library/react';
import { performance } from 'perf_hooks';
import LargeList from '../LargeList';
describe('Performance Tests', () => {
  const generateLargeDataset = (size) => {
    return Array.from({ length: size }, (_, index) => ({
      id: index,
      name: `Item ${index}`,
      description: `Description for item ${index}`,
      value: Math.random() * 1000
    }));
  };
  
  it('renders large list efficiently', () => {
    const data = generateLargeDataset(10000);
    
    const startTime = performance.now();
    render(<LargeList items={data} />);
    const endTime = performance.now();
    
    const renderTime = endTime - startTime;
    
    // 渲染时间应该少于 100ms
    expect(renderTime).toBeLessThan(100);
  });
  
  it('handles frequent updates efficiently', async () => {
    const data = generateLargeDataset(1000);
    const { rerender } = render(<LargeList items={data} />);
    
    const startTime = performance.now();
    
    // 模拟频繁更新
    for (let i = 0; i < 100; i++) {
      const updatedData = data.map(item => ({
        ...item,
        value: Math.random() * 1000
      }));
      rerender(<LargeList items={updatedData} />);
    }
    
    const endTime = performance.now();
    const updateTime = endTime - startTime;
    
    // 100次更新应该在 500ms 内完成
    expect(updateTime).toBeLessThan(500);
  });
  
  it('memory usage stays within bounds', () => {
    const initialMemory = process.memoryUsage().heapUsed;
    
    const data = generateLargeDataset(5000);
    const { unmount } = render(<LargeList items={data} />);
    
    const afterRenderMemory = process.memoryUsage().heapUsed;
    const memoryIncrease = afterRenderMemory - initialMemory;
    
    unmount();
    
    // 强制垃圾回收(如果可用)
    if (global.gc) {
      global.gc();
    }
    
    const afterUnmountMemory = process.memoryUsage().heapUsed;
    
    // 内存增长应该合理(小于 50MB)
    expect(memoryIncrease).toBeLessThan(50 * 1024 * 1024);
    
    // 卸载后内存应该释放大部分
    const memoryLeakage = afterUnmountMemory - initialMemory;
    expect(memoryLeakage).toBeLessThan(memoryIncrease * 0.1);
  });
});最佳实践 
测试策略 
- 测试金字塔:70% 单元测试,20% 集成测试,10% E2E 测试
- 测试驱动开发:先写测试,再写实现
- 行为驱动测试:测试用户行为而不是实现细节
- 快速反馈:保持测试快速运行
- 可维护性:编写清晰、可读的测试代码
测试命名规范 
javascript
// 好的测试命名
describe('UserService', () => {
  describe('when user is authenticated', () => {
    it('should return user profile', () => {});
    it('should handle API errors gracefully', () => {});
  });
  
  describe('when user is not authenticated', () => {
    it('should redirect to login page', () => {});
    it('should clear user data', () => {});
  });
});
// 使用 Given-When-Then 模式
it('should display error message when login fails', async () => {
  // Given: 用户在登录页面
  render(<LoginPage />);
  
  // When: 用户输入错误的凭据并提交
  await userEvent.type(screen.getByLabelText(/email/i), 'wrong@email.com');
  await userEvent.type(screen.getByLabelText(/password/i), 'wrongpassword');
  await userEvent.click(screen.getByRole('button', { name: /login/i }));
  
  // Then: 应该显示错误消息
  await expect(screen.findByText(/invalid credentials/i)).resolves.toBeInTheDocument();
});测试数据管理 
javascript
// src/test/fixtures/index.js
export const userFixtures = {
  regularUser: {
    id: 1,
    name: 'John Doe',
    email: 'john@example.com',
    roles: ['user'],
    permissions: ['read:projects']
  },
  
  adminUser: {
    id: 2,
    name: 'Admin User',
    email: 'admin@example.com',
    roles: ['admin'],
    permissions: ['read:projects', 'create:projects', 'update:projects', 'delete:projects']
  }
};
export const projectFixtures = {
  activeProject: {
    id: 1,
    name: 'Active Project',
    description: 'An active project',
    status: 'active',
    owner: userFixtures.regularUser,
    createdAt: '2023-01-01T00:00:00Z'
  },
  
  archivedProject: {
    id: 2,
    name: 'Archived Project',
    description: 'An archived project',
    status: 'archived',
    owner: userFixtures.adminUser,
    createdAt: '2022-01-01T00:00:00Z'
  }
};
// 工厂函数
export const createUser = (overrides = {}) => ({
  ...userFixtures.regularUser,
  ...overrides
});
export const createProject = (overrides = {}) => ({
  ...projectFixtures.activeProject,
  ...overrides
});通过遵循这些测试指南和最佳实践,您可以构建出高质量、可维护的测试套件,确保 Trae 应用程序的稳定性和可靠性。