# 集成测试最佳实践
## 概述
本文档提供了在项目中编写和维护集成测试的最佳实践指南。集成测试用于验证多个组件或服务之间的协作是否正确。
## 测试类型区分
### 单元测试 vs 集成测试
| 测试类型 | 测试范围 | 使用场景 |
|---------|---------|---------|
| **单元测试** | 单个函数、方法、类 | 验证独立逻辑的正确性 |
| **集成测试** | 多个组件、服务协作 | 验证系统各部分集成是否正确 |
| **端到端测试** | 完整用户流程 | 验证从用户界面到后端的完整流程 |
## API集成测试模式
### 基本测试结构
```typescript
describe('API Endpoint Integration', () => {
let app: Hono;
let apiClient: ApiClient;
beforeEach(async () => {
// 设置测试环境
vi.clearAllMocks();
// 创建测试应用
app = new Hono();
// 注册路由
const routes = await import('./routes');
routes.default(app);
// 创建API客户端
apiClient = createApiClient(app);
});
it('should return correct response', async () => {
// 设置mock
const mockService = require('./service').default;
mockService.doSomething.mockResolvedValue({ data: 'test' });
// 执行请求
const response = await apiClient.get('/endpoint');
// 验证结果
expect(response.status).toBe(200);
expect(response.data).toEqual({ data: 'test' });
expect(mockService.doSomething).toHaveBeenCalled();
});
});
```
### 认证和授权测试
```typescript
describe('Authentication', () => {
it('should require authentication', async () => {
const response = await apiClient.get('/protected', {}, { authToken: undefined });
expect(response.status).toBe(401);
});
it('should require specific roles', async () => {
// Mock用户具有不同角色
const response = await apiClient.get('/admin-only');
expect(response.status).toBe(403);
});
});
```
### 错误处理测试
```typescript
describe('Error Handling', () => {
it('should handle service errors gracefully', async () => {
const mockService = require('./service').default;
mockService.doSomething.mockRejectedValue(new Error('Service unavailable'));
const response = await apiClient.get('/endpoint');
expect(response.status).toBe(500);
expect(response.data).toMatchObject({
code: 500,
message: 'Service unavailable'
});
});
it('should handle validation errors', async () => {
const response = await apiClient.post('/endpoint', { invalid: 'data' });
expect(response.status).toBe(400);
expect(response.data).toHaveProperty('error');
});
});
```
## React组件集成测试模式
### 组件渲染测试
```typescript
describe('Component Integration', () => {
it('should render with correct props', () => {
render(
);
expect(screen.getByText('Test')).toBeInTheDocument();
expect(screen.getByRole('button')).toBeEnabled();
});
it('should handle user interactions', async () => {
const user = userEvent.setup();
const handleClick = vi.fn();
render(
);
await user.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
});
```
### 路由和导航测试
```typescript
describe('Routing', () => {
it('should navigate to correct route', async () => {
renderWithRouter(, { route: '/login' });
expect(screen.getByLabelText('Email')).toBeInTheDocument();
expect(screen.getByLabelText('Password')).toBeInTheDocument();
});
it('should handle protected routes', () => {
renderWithRouter(, { route: '/admin' });
// 未认证用户应该被重定向
expect(screen.getByText('Redirecting to login...')).toBeInTheDocument();
});
});
```
### 状态管理测试
```typescript
describe('State Management', () => {
it('should update UI based on state changes', async () => {
render(
);
// 初始状态
expect(screen.getByText('Loading...')).toBeInTheDocument();
// 等待数据加载
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument();
});
});
});
```
## Mock策略
### 数据库Mock
```typescript
// 使用内存数据库
const testDataSource = new DataSource({
type: 'better-sqlite3',
database: ':memory:',
synchronize: true,
entities: [User, Role]
});
// 或者使用mock repository
const mockRepo = {
find: vi.fn().mockResolvedValue([]),
findOne: vi.fn().mockResolvedValue(null),
save: vi.fn().mockResolvedValue({ id: 1 })
};
```
### 外部服务Mock
```typescript
// 使用MSW mock HTTP请求
import { setupServer } from 'msw/node';
import { rest } from 'msw';
const server = setupServer(
rest.get('/api/users', (req, res, ctx) => {
return res(ctx.json([{ id: 1, name: 'Test User' }]));
})
);
// 或者使用vi.mock
vi.mock('../external-service', () => ({
fetchData: vi.fn().mockResolvedValue({ data: 'test' })
}));
```
### 认证Mock
```typescript
// Mock JWT验证
vi.mock('../auth/middleware', () => ({
authMiddleware: vi.fn().mockImplementation((c, next) => {
c.set('user', { id: 1, username: 'testuser' });
return next();
})
}));
```
## 测试数据管理
### 测试数据工厂
```typescript
export function createTestUser(overrides: Partial = {}): User {
return {
id: 1,
username: 'testuser',
email: 'test@example.com',
password: 'hashed_password',
createdAt: new Date(),
updatedAt: new Date(),
...overrides
};
}
// 使用工厂创建测试数据
const adminUser = createTestUser({ username: 'admin', roles: ['admin'] });
const inactiveUser = createTestUser({ status: 'inactive' });
```
### 测试数据清理
```typescript
afterEach(async () => {
// 清理测试数据
await testDataSource.getRepository(User).clear();
await testDataSource.getRepository(Role).clear();
});
afterAll(async () => {
// 关闭数据库连接
await testDataSource.destroy();
});
```
## 性能优化
### 测试并行化
```typescript
// 在vitest.config.ts中配置
export default defineConfig({
test: {
maxThreads: 4,
minThreads: 2,
fileParallelism: true
}
});
```
### 测试数据隔离
```typescript
// 使用事务确保测试隔离
describe('User Tests', () => {
let dataSource: DataSource;
let queryRunner: QueryRunner;
beforeEach(async () => {
dataSource = await createTestDataSource();
queryRunner = dataSource.createQueryRunner();
await queryRunner.startTransaction();
});
afterEach(async () => {
await queryRunner.rollbackTransaction();
await queryRunner.release();
});
afterAll(async () => {
await dataSource.destroy();
});
});
```
## 常见陷阱和解决方案
### 1. 测试相互干扰
**问题**: 一个测试修改了全局状态,影响其他测试
**解决方案**: 使用`beforeEach`和`afterEach`清理状态
### 2. 测试执行缓慢
**问题**: 集成测试执行时间过长
**解决方案**: 使用内存数据库,mock外部服务
### 3. 测试不稳定
**问题**: 测试有时通过,有时失败
**解决方案**: 避免依赖时间、随机数,使用固定测试数据
### 4. Mock过于复杂
**问题**: Mock代码比实际代码还复杂
**解决方案**: 使用工具函数简化mock创建
## 代码质量检查
### 测试覆盖率目标
- 行覆盖率: > 70%
- 分支覆盖率: > 70%
- 函数覆盖率: > 70%
- 语句覆盖率: > 70%
### 代码审查清单
- [ ] 测试名称清晰描述预期行为
- [ ] 使用恰当的断言
- [ ] 包含错误场景测试
- [ ] 测试数据准备充分
- [ ] 测试清理逻辑完整
- [ ] 没有重复的测试代码
- [ ] 遵循AAA模式(Arrange-Act-Assert)
## 工具和配置
### 推荐工具
- **测试框架**: Vitest
- **API测试**: Supertest + 自定义API客户端
- **组件测试**: Testing Library + Happy DOM
- **HTTP Mock**: MSW (Mock Service Worker)
- **数据库**: SQLite内存数据库
### 配置文件示例
```typescript
// vitest.config.ts
export default defineConfig({
test: {
environment: 'node',
include: ['**/__integration_tests__/**/*.test.ts'],
setupFiles: ['./src/test/setup.ts'],
globals: true
}
});
```
## 总结
集成测试是确保系统各部分正确协作的关键。遵循这些最佳实践可以编写出可靠、可维护的集成测试。记住测试的目标是增加信心,而不是追求100%的覆盖率。专注于测试关键业务逻辑和集成点。