▸ Agent Skills
1 min read

Good and Bad Tests

Good tests

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

// GOOD: Tests observable behaviour
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 behaviour 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.

// BAD: Tests implementation details
test("checkout calls paymentService.process", async () => {
  const mockPayment = 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 behaviour change
  • Test name describes HOW not WHAT
  • Verifying through external means instead of the interface
// 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")
})

Tautological tests: expected value restates the implementation, so the test passes by construction.

// BAD: Expected value is recomputed the way the code computes it
test("calculateTotal sums line items", () => {
  const items = [{price: 10}, {price: 5}]
  const expected = items.reduce((sum, i) => sum + i.price, 0)
  expect(calculateTotal(items)).toBe(expected)
})

// GOOD: Expected value is an independent, known literal
test("calculateTotal sums line items", () => {
  expect(calculateTotal([{price: 10}, {price: 5}])).toBe(15)
})

Last updated Oct 08, 2026