Claude
Skills
Sign in
Back

testing-strategy

Included with Lifetime
$97 forever

Comprehensive testing strategies including test pyramid, TDD methodology, testing patterns, coverage goals, and CI/CD integration. Use when writing tests, implementing TDD, reviewing test coverage, debugging test failures, or setting up testing infrastructure.

Cloud & DevOps

What this skill does


# Testing Strategy

This skill provides comprehensive guidance for implementing effective testing strategies across your entire application stack.

## Test Pyramid

### The Testing Hierarchy

```
        /\
       /  \
      /E2E \       10% - End-to-End Tests (slowest, most expensive)
     /______\
    /        \
   /Integration\  20% - Integration Tests (medium speed/cost)
  /____________\
 /              \
/   Unit Tests   \ 70% - Unit Tests (fast, cheap, focused)
/__________________\
```

**Rationale**:
- **70% Unit Tests**: Fast, isolated, catch bugs early
- **20% Integration Tests**: Test component interactions
- **10% E2E Tests**: Test critical user journeys

### Why This Distribution?

**Unit tests are cheap**:
- Run in milliseconds
- No external dependencies
- Easy to debug
- High code coverage per test

**Integration tests are moderate**:
- Test real interactions
- Catch integration bugs
- Slower than unit tests
- More complex setup

**E2E tests are expensive**:
- Test entire system
- Catch UX issues
- Very slow (seconds/minutes)
- Brittle and hard to maintain

## TDD (Test-Driven Development)

### Red-Green-Refactor Cycle

**1. Red - Write a failing test**:
```typescript
describe('Calculator', () => {
  test('adds two numbers', () => {
    const calculator = new Calculator();
    expect(calculator.add(2, 3)).toBe(5); // FAILS - method doesn't exist
  });
});
```

**2. Green - Write minimal code to pass**:
```typescript
class Calculator {
  add(a: number, b: number): number {
    return a + b; // Simplest implementation
  }
}
// Test now PASSES
```

**3. Refactor - Improve the code**:
```typescript
class Calculator {
  add(a: number, b: number): number {
    // Add validation
    if (!Number.isFinite(a) || !Number.isFinite(b)) {
      throw new Error('Arguments must be finite numbers');
    }
    return a + b;
  }
}
```

### TDD Benefits

**Design benefits**:
- Forces you to think about API before implementation
- Leads to more testable, modular code
- Encourages SOLID principles

**Quality benefits**:
- 100% test coverage by design
- Catches bugs immediately
- Provides living documentation

**Workflow benefits**:
- Clear next step (make test pass)
- Confidence when refactoring
- Prevents over-engineering

## Arrange-Act-Assert Pattern

### The AAA Pattern

Every test should follow this structure:

```typescript
test('user registration creates account and sends welcome email', async () => {
  // ARRANGE - Set up test conditions
  const userData = {
    email: '[email protected]',
    password: 'SecurePass123',
    name: 'Test User',
  };
  const mockEmailService = jest.fn();
  const userService = new UserService(mockEmailService);

  // ACT - Execute the behavior being tested
  const result = await userService.register(userData);

  // ASSERT - Verify the outcome
  expect(result.id).toBeDefined();
  expect(result.email).toBe(userData.email);
  expect(mockEmailService).toHaveBeenCalledWith({
    to: userData.email,
    subject: 'Welcome!',
    template: 'welcome',
  });
});
```

### Why AAA?

- **Clear structure**: Easy to understand what's being tested
- **Consistent**: All tests follow same pattern
- **Maintainable**: Easy to modify and debug

## Mocking Strategies

### When to Mock

**✅ DO mock**:
- External APIs
- Databases
- File system operations
- Time/dates
- Random number generators
- Network requests
- Third-party services

```typescript
// Mock external API
jest.mock('axios');

test('fetches user data from API', async () => {
  const mockData = { id: 1, name: 'John' };
  (axios.get as jest.Mock).mockResolvedValue({ data: mockData });

  const user = await fetchUser(1);

  expect(user).toEqual(mockData);
});
```

### When NOT to Mock

**❌ DON'T mock**:
- Pure functions (test them directly)
- Simple utility functions
- Domain logic
- Value objects
- Internal implementation details

```typescript
// ❌ BAD - Over-mocking
test('validates email', () => {
  const validator = new EmailValidator();
  jest.spyOn(validator, 'isValid').mockReturnValue(true);
  expect(validator.isValid('[email protected]')).toBe(true);
  // This test is useless - you're testing the mock, not the code
});

// ✅ GOOD - Test real implementation
test('validates email', () => {
  const validator = new EmailValidator();
  expect(validator.isValid('[email protected]')).toBe(true);
  expect(validator.isValid('invalid')).toBe(false);
});
```

### Mocking Patterns

**Stub** (return predetermined values):
```typescript
const mockDatabase = {
  findUser: jest.fn().mockResolvedValue({ id: 1, name: 'John' }),
  saveUser: jest.fn().mockResolvedValue(true),
};
```

**Spy** (track calls, use real implementation):
```typescript
const emailService = new EmailService();
const sendSpy = jest.spyOn(emailService, 'send');

await emailService.send('[email protected]', 'Hello');

expect(sendSpy).toHaveBeenCalledTimes(1);
expect(sendSpy).toHaveBeenCalledWith('[email protected]', 'Hello');
```

**Fake** (lightweight implementation):
```typescript
class FakeDatabase {
  private data = new Map();

  async save(key: string, value: any) {
    this.data.set(key, value);
  }

  async get(key: string) {
    return this.data.get(key);
  }
}
```

## Test Coverage Goals

### Coverage Metrics

**Line Coverage**: Percentage of code lines executed
- **Target**: 80-90% for critical paths

**Branch Coverage**: Percentage of if/else branches tested
- **Target**: 80%+ (more important than line coverage)

**Function Coverage**: Percentage of functions called
- **Target**: 90%+

**Statement Coverage**: Percentage of statements executed
- **Target**: 80%+

### Coverage Configuration

```json
// package.json
{
  "jest": {
    "collectCoverage": true,
    "coverageThreshold": {
      "global": {
        "branches": 80,
        "functions": 90,
        "lines": 80,
        "statements": 80
      },
      "./src/critical/": {
        "branches": 95,
        "functions": 95,
        "lines": 95,
        "statements": 95
      }
    },
    "coveragePathIgnorePatterns": [
      "/node_modules/",
      "/tests/",
      "/migrations/",
      "/.config.ts$/"
    ]
  }
}
```

### What to Prioritize

**High priority** (aim for 95%+ coverage):
- Business logic
- Security-critical code
- Payment/billing code
- Data validation
- Authentication/authorization

**Medium priority** (aim for 80%+ coverage):
- API endpoints
- Database queries
- Utility functions
- Error handling

**Low priority** (optional coverage):
- UI components (use integration tests instead)
- Configuration files
- Type definitions
- Third-party library wrappers

## Integration Testing

### Database Integration Tests

```typescript
import { PrismaClient } from '@prisma/client';

describe('UserRepository', () => {
  let prisma: PrismaClient;
  let repository: UserRepository;

  beforeAll(async () => {
    // Use test database
    prisma = new PrismaClient({
      datasources: { db: { url: process.env.TEST_DATABASE_URL } },
    });
    repository = new UserRepository(prisma);
  });

  beforeEach(async () => {
    // Clean database before each test
    await prisma.user.deleteMany();
  });

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

  test('creates user and retrieves by email', async () => {
    // ARRANGE
    const userData = {
      email: '[email protected]',
      name: 'Test User',
      password: 'hashed_password',
    };

    // ACT
    const created = await repository.create(userData);
    const retrieved = await repository.findByEmail(userData.email);

    // ASSERT
    expect(retrieved).toBeDefined();
    expect(retrieved?.id).toBe(created.id);
    expect(retrieved?.email).toBe(userData.email);
  });
});
```

### API Integration Tests

```typescript
import request from 'supertest';
import { app } from '../src/app';

describe('User API', () => {
  test('POST /api/users creates user and returns 201', async () => {
    const response = await request(app)
      .post('/api/users')
      .send({
        email: '[email protected]',
    

Related in Cloud & DevOps