Back to Sanity

Good and Bad Tests

.agents/skills/tdd/tests.md

5.24.01.6 KB
Original Source

Good and Bad Tests

Good Tests

Integration-style: Test through real interfaces, not mocks of internal parts.

typescript
// GOOD: Tests observable behavior
test('user can checkout with valid cart', async () => {
  const cart = createCart()
  cart.add(product)
  const result = await checkout(cart, paymentMethod)
  expect(result.status).toBe('confirmed')
})

Characteristics:

  • Tests behavior users/callers care about
  • Uses public API only
  • Survives internal refactors
  • Describes WHAT, not HOW
  • One logical assertion per test

Bad Tests

Implementation-detail tests: Coupled to internal structure.

typescript
// BAD: Tests implementation details
test('checkout calls paymentService.process', async () => {
  const mockPayment = jest.mock(paymentService)
  await checkout(cart, payment)
  expect(mockPayment.process).toHaveBeenCalledWith(cart.total)
})

Red flags:

  • Mocking internal collaborators
  • Testing private methods
  • Asserting on call counts/order
  • Test breaks when refactoring without behavior change
  • Test name describes HOW not WHAT
  • Verifying through external means instead of interface
typescript
// BAD: Bypasses interface to verify
test('createUser saves to database', async () => {
  await createUser({name: 'Alice'})
  const row = await db.query('SELECT * FROM users WHERE name = ?', ['Alice'])
  expect(row).toBeDefined()
})

// GOOD: Verifies through interface
test('createUser makes user retrievable', async () => {
  const user = await createUser({name: 'Alice'})
  const retrieved = await getUser(user.id)
  expect(retrieved.name).toBe('Alice')
})