# What a good test is

A test verifies behaviour through a public interface. The code underneath can change entirely; the
test should not. A good one reads like a specification: `user can checkout with valid cart` tells you
exactly what capability exists, and survives refactors because it does not care about internal
structure.

## Good

```typescript
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");
});
```

- Tests what a caller cares about
- Uses the public interface only
- Survives internal refactors
- Describes *what*, not *how*
- One logical assertion

## The three anti-patterns

### Implementation-coupled

Mocks internal collaborators, reaches for private methods, or asserts on call counts and ordering.
**The tell: the test breaks when you refactor, even though the behaviour did not change.**

```typescript
// BAD: asserts on the mock, not the behaviour
test("checkout calls paymentService.process", async () => {
  const mockPayment = vi.mock(paymentService);
  await checkout(cart, payment);
  expect(mockPayment.process).toHaveBeenCalledWith(cart.total);
});
```

A second form is verifying through a side channel instead of the interface:

```typescript
// BAD: bypasses the interface to check the database directly
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 the interface a caller would use
test("createUser makes the user retrievable", async () => {
  const user = await createUser({ name: "Alice" });
  const retrieved = await getUser(user.id);
  expect(retrieved.name).toBe("Alice");
});
```

### Tautological

The expected value is recomputed the way the code computes it, so the test passes by construction and
can never disagree with the implementation.

```typescript
// BAD: the assertion reimplements the function
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: an independent, known literal
test("calculateTotal sums line items", () => {
  expect(calculateTotal([{ price: 10 }, { price: 5 }])).toBe(15);
});
```

Expected values come from an independent source of truth: a known-good literal, a worked example, or
the spec. Never from the code.

### Horizontal slicing

Writing all the tests first, then all the implementation. Covered in `SKILL.md`; the fix is vertical
slices, one test and one implementation at a time.

## Before writing any test

Name the production change that would make this test fail. If you cannot name one, the test asserts
nothing and should not be written.

## Keep test-only code out of production

A method that exists so a test can reach inside is a hole in the interface. Put helpers in test
utilities. If the test cannot reach what it needs through the interface, that is a design finding,
not a reason to widen the interface.
