Claude
Skills
Sign in
Back

testing-patterns

Included with Lifetime
$97 forever

Patrons et stratégies de test complets pour les projets JavaScript/TypeScript. Couvre les tests unitaires, d'intégration et E2E, les stratégies de mocking, l'organisation des tests et les anti-patrons courants. À utiliser quand l'utilisateur veut écrire des tests, améliorer la couverture de tests, établir une stratégie de test ou corriger des tests instables.

Code Review

What this skill does


# Testing Patterns & Strategies

Comprehensive guide to testing JavaScript/TypeScript applications. Complements the `tdd` skill (which focuses on workflow) by providing concrete patterns, strategies, and anti-patterns.

## When to Use This Skill

Activate when the user:
- Wants to write tests for existing or new code
- Needs to establish a testing strategy for a project
- Wants to improve test coverage or reliability
- Has flaky or brittle tests
- Asks about mocking, stubbing, or test doubles
- Needs guidance on unit vs integration vs E2E tests

## Testing Pyramid

```
        /  E2E  \          Few, slow, high confidence
       /----------\
      / Integration \      Moderate count, medium speed
     /----------------\
    /      Unit        \   Many, fast, focused
   /____________________\
```

**Rule of thumb:** ~70% unit, ~20% integration, ~10% E2E. Adjust per project — legacy codebases often benefit from more integration tests initially.

## 1. Unit Testing Patterns

### Test Structure: AAA (Arrange, Act, Assert)

```typescript
describe('calculateDiscount', () => {
  it('should apply 10% discount for orders above 100€', () => {
    // Arrange
    const order = { total: 150, items: ['item1', 'item2'] }

    // Act
    const result = calculateDiscount(order)

    // Assert
    expect(result).toBe(135)
  })
})
```

### Naming Convention

Use descriptive names that read as specifications:

```typescript
// GOOD — describes behavior
it('should return 401 when token is expired')
it('should retry 3 times before failing')
it('should trim whitespace from patient name')

// BAD — describes implementation
it('should call validateToken')
it('should set retryCount to 3')
it('should use .trim()')
```

### Parameterized Tests (Table-Driven)

```typescript
describe('parseHL7Date', () => {
  it.each([
    ['20260315', new Date(2026, 2, 15)],
    ['20260315120000', new Date(2026, 2, 15, 12, 0, 0)],
    ['', null],
    ['invalid', null],
  ])('should parse "%s" to %s', (input, expected) => {
    expect(parseHL7Date(input)).toEqual(expected)
  })
})
```

### Testing Error Cases

Always test the unhappy path:

```typescript
describe('PatientService.findById', () => {
  it('should throw NotFoundError for unknown patient ID', async () => {
    await expect(service.findById('UNKNOWN'))
      .rejects.toThrow(NotFoundError)
  })

  it('should throw ValidationError for empty ID', async () => {
    await expect(service.findById(''))
      .rejects.toThrow(ValidationError)
  })
})
```

### Testing Async Code

```typescript
// Promises
it('should resolve with patient data', async () => {
  const patient = await fetchPatient('PAT123')
  expect(patient.name).toBe('DUPONT')
})

// Event emitters
it('should emit "transfer" event on unit change', (done) => {
  emitter.on('transfer', (data) => {
    expect(data.unit).toBe('CARDIO')
    done()
  })
  service.transferPatient('PAT123', 'CARDIO')
})
```

## 2. Integration Testing Patterns

Integration tests verify that components work together correctly.

### Database Integration Tests

```typescript
describe('PatientRepository', () => {
  let db: TestDatabase

  beforeAll(async () => {
    db = await TestDatabase.create()  // Real DB, not mock
    await db.migrate()
  })

  afterEach(async () => {
    await db.truncate()  // Clean between tests
  })

  afterAll(async () => {
    await db.destroy()
  })

  it('should persist and retrieve a patient', async () => {
    const repo = new PatientRepository(db.connection)

    await repo.create({
      id: 'PAT123',
      name: 'DUPONT',
      birthDate: '1975-03-15',
    })

    const found = await repo.findById('PAT123')
    expect(found).toMatchObject({
      id: 'PAT123',
      name: 'DUPONT',
    })
  })
})
```

### API Integration Tests

```typescript
describe('POST /api/patients', () => {
  it('should create a patient and return 201', async () => {
    const response = await request(app)
      .post('/api/patients')
      .send({ name: 'DUPONT', birthDate: '1975-03-15' })
      .set('Authorization', `Bearer ${validToken}`)

    expect(response.status).toBe(201)
    expect(response.body.data.id).toBeDefined()
  })

  it('should return 400 for missing required fields', async () => {
    const response = await request(app)
      .post('/api/patients')
      .send({})  // Missing name
      .set('Authorization', `Bearer ${validToken}`)

    expect(response.status).toBe(400)
    expect(response.body.errors).toContainEqual(
      expect.objectContaining({ field: 'name' })
    )
  })

  it('should return 401 without authentication', async () => {
    const response = await request(app)
      .post('/api/patients')
      .send({ name: 'DUPONT' })

    expect(response.status).toBe(401)
  })
})
```

### Message Processing Integration Tests

Particularly relevant for healthcare message processing (HPK, HL7):

```typescript
describe('HPK Message Pipeline', () => {
  it('should parse, validate, and transform an ID|M1 message', async () => {
    const rawMessage = 'ID|M1|C|HEXAGONE|20260122120000|USER001|PAT123|DUPONT|JEAN|19750315|M'

    const result = await pipeline.process(rawMessage)

    expect(result.type).toBe('ID')
    expect(result.code).toBe('M1')
    expect(result.patient.name).toBe('DUPONT')
    expect(result.valid).toBe(true)
  })
})
```

## 3. E2E Testing Patterns

E2E tests validate complete user workflows through the real application.

### Page Object Pattern

```typescript
class PatientListPage {
  constructor(private page: Page) {}

  async goto() {
    await this.page.goto('/patients')
  }

  async search(query: string) {
    await this.page.fill('[data-testid="search-input"]', query)
    await this.page.click('[data-testid="search-button"]')
  }

  async getResults() {
    return this.page.locator('[data-testid="patient-row"]').allTextContents()
  }

  async clickPatient(name: string) {
    await this.page.click(`text=${name}`)
  }
}

// Usage in test
test('should find patient by name', async ({ page }) => {
  const patientList = new PatientListPage(page)
  await patientList.goto()
  await patientList.search('DUPONT')

  const results = await patientList.getResults()
  expect(results).toContain('DUPONT JEAN')
})
```

### Data-testid Convention

Use `data-testid` attributes for test selectors, never CSS classes or element structure:

```typescript
// GOOD — stable selector
await page.click('[data-testid="submit-admission"]')

// BAD — brittle, breaks on style/structure changes
await page.click('.btn.btn-primary.submit')
await page.click('form > div:nth-child(3) > button')
```

### Visual Regression Testing

```typescript
test('patient dashboard matches snapshot', async ({ page }) => {
  await page.goto('/dashboard')
  await page.waitForLoadState('networkidle')
  await expect(page).toHaveScreenshot('dashboard.png', {
    maxDiffPixelRatio: 0.01,
  })
})
```

## 4. Mocking Strategies

### When to Mock

| Mock | Don't Mock |
|------|------------|
| External HTTP APIs | Your own database (use real test DB) |
| Time/Date (for determinism) | Internal collaborators between modules |
| File system (when impractical) | Simple utility functions |
| Third-party services (payment, email) | Data transformations |
| Environment-specific features | Business logic |

### Mock vs Stub vs Spy

```typescript
// STUB — returns a fixed value
const getPatient = vi.fn().mockResolvedValue({ id: 'PAT123', name: 'DUPONT' })

// SPY — observes calls without changing behavior
const logSpy = vi.spyOn(logger, 'info')
await service.admitPatient(data)
expect(logSpy).toHaveBeenCalledWith('Patient admitted', { id: 'PAT123' })

// MOCK — replaces the entire module
vi.mock('./emailService', () => ({
  sendAdmissionNotification: vi.fn().mockResolvedValue(true),
}))
```

### MSW for HTTP Mocking

Prefer MSW (Mock Service Worker) over manual fetch mocking:

```typescript
import { http, HttpResponse } from 'msw'
import { setupServer } from 'msw/node'

const server = setupServer(
  http.get('/api/patients/:id', ({

Related in Code Review