Skip to main content
ClaudeWave
Skill545 estrellas del repoactualizado 9d ago

backend/testing-guide

This Claude Code skill provides a comprehensive backend testing guide covering unit tests, integration tests, and end-to-end tests with specific implementation patterns. Use this when developers need structured guidance on writing backend tests following the testing pyramid approach, including Service layer tests with mocked repositories, business logic validation, and complete API flow verification required before QA validation.

Instalar en Claude Code
Copiar
git clone --depth 1 https://github.com/echoVic/boss-skill /tmp/backend-testing-guide && cp -r /tmp/backend-testing-guide/skill/skills/backend/testing-guide ~/.claude/skills/backend-testing-guide
Después abre una sesión nueva de Claude Code; el skill carga automáticamente.

SKILL.md

# 后端测试编写指南

## 测试要求(强制)

> **职责边界**:Backend Agent 是测试的**编写者**,QA Agent 是测试的**验证者**。

### 测试金字塔

| 测试类型 | 占比 | 要求 |
|----------|------|------|
| **单元测试** | ~70% | Service 层、业务逻辑必须有测试 |
| **集成测试** | ~20% | API 端点、数据库操作测试 |
| **E2E 测试** | ~10% | **必须编写**,完整 API 流程测试 |

## 单元测试编写

### Service 层测试

```typescript
// services/userService.test.ts
import { UserService } from './userService';
import { UserRepository } from '../repositories/userRepository';

jest.mock('../repositories/userRepository');

describe('UserService', () => {
  let userService: UserService;
  let userRepository: jest.Mocked<UserRepository>;

  beforeEach(() => {
    userRepository = new UserRepository() as jest.Mocked<UserRepository>;
    userService = new UserService(userRepository);
  });

  describe('getById', () => {
    it('returns user when found', async () => {
      const mockUser = { id: '1', name: 'Alice', email: 'alice@example.com' };
      userRepository.findById.mockResolvedValue(mockUser);

      const result = await userService.getById('1');

      expect(result).toEqual(mockUser);
      expect(userRepository.findById).toHaveBeenCalledWith('1');
    });

    it('throws NotFoundError when user not found', async () => {
      userRepository.findById.mockResolvedValue(null);

      await expect(userService.getById('999')).rejects.toThrow('User not found');
    });
  });

  describe('create', () => {
    it('creates user with valid data', async () => {
      const createData = { name: 'Bob', email: 'bob@example.com' };
      const mockUser = { id: '2', ...createData };
      
      userRepository.findByEmail.mockResolvedValue(null);
      userRepository.create.mockResolvedValue(mockUser);

      const result = await userService.create(createData);

      expect(result).toEqual(mockUser);
      expect(userRepository.findByEmail).toHaveBeenCalledWith('bob@example.com');
      expect(userRepository.create).toHaveBeenCalledWith(createData);
    });

    it('throws ConflictError when email already exists', async () => {
      const createData = { name: 'Bob', email: 'existing@example.com' };
      userRepository.findByEmail.mockResolvedValue({ id: '1', name: 'Existing', email: 'existing@example.com' });

      await expect(userService.create(createData)).rejects.toThrow('Email already exists');
    });
  });
});
```

### 业务逻辑测试

```typescript
// services/orderService.test.ts
describe('OrderService', () => {
  describe('calculateTotal', () => {
    it('calculates total with discount', () => {
      const items = [
        { price: 100, quantity: 2 },
        { price: 50, quantity: 1 },
      ];
      const discount = 0.1; // 10% off

      const total = orderService.calculateTotal(items, discount);

      expect(total).toBe(225); // (200 + 50) * 0.9
    });

    it('handles zero discount', () => {
      const items = [{ price: 100, quantity: 1 }];
      const total = orderService.calculateTotal(items, 0);
      expect(total).toBe(100);
    });
  });

  describe('validateOrder', () => {
    it('validates order with sufficient stock', async () => {
      const order = { productId: '1', quantity: 5 };
      productRepository.findById.mockResolvedValue({ id: '1', stock: 10 });

      const result = await orderService.validateOrder(order);

      expect(result.valid).toBe(true);
    });

    it('rejects order with insufficient stock', async () => {
      const order = { productId: '1', quantity: 15 };
      productRepository.findById.mockResolvedValue({ id: '1', stock: 10 });

      const result = await orderService.validateOrder(order);

      expect(result.valid).toBe(false);
      expect(result.error).toBe('Insufficient stock');
    });
  });
});
```

## 集成测试编写

### API 端点测试

```typescript
// controllers/userController.test.ts
import request from 'supertest';
import { app } from '../app';
import { db } from '../db';

describe('User API', () => {
  beforeEach(async () => {
    // 清理测试数据库
    await db.user.deleteMany();
  });

  afterAll(async () => {
    await db.$disconnect();
  });

  describe('POST /api/users', () => {
    it('creates a new user', async () => {
      const userData = {
        name: 'Alice',
        email: 'alice@example.com',
      };

      const response = await request(app)
        .post('/api/users')
        .send(userData)
        .expect(201);

      expect(response.body.success).toBe(true);
      expect(response.body.data).toMatchObject(userData);
      expect(response.body.data.id).toBeDefined();
    });

    it('returns 400 for invalid email', async () => {
      const userData = {
        name: 'Bob',
        email: 'invalid-email',
      };

      const response = await request(app)
        .post('/api/users')
        .send(userData)
        .expect(400);

      expect(response.body.success).toBe(false);
      expect(response.body.error.code).toBe('VALIDATION_ERROR');
    });

    it('returns 409 for duplicate email', async () => {
      const userData = {
        name: 'Charlie',
        email: 'duplicate@example.com',
      };

      // 创建第一个用户
      await request(app).post('/api/users').send(userData);

      // 尝试创建重复邮箱的用户
      const response = await request(app)
        .post('/api/users')
        .send(userData)
        .expect(409);

      expect(response.body.error.code).toBe('CONFLICT');
    });
  });

  describe('GET /api/users/:id', () => {
    it('returns user by id', async () => {
      // 创建测试用户
      const createResponse = await request(app)
        .post('/api/users')
        .send({ name: 'Dave', email: 'dave@example.com' });

      const userId = createResponse.body.data.id;

      // 获取用户
      const response = await request(app)
        .get(`/api/users/${userId}`)
        .expect(200);

      expect(response.body.data.id).toBe(userId);
      expect(response.body.data.name).toBe('Dave');
    });

    it('returns 404 for non-existent user', async () => {
      const response = await request(app)
        .get('/api/users/non-existent-id')
        .expect(404);

      ex