What to test

Test what the thing does, so the suite survives the next rewrite of how it does it.

05of 16
≈ 13Min read
5Parts

The distinction that decides everything

A test coupled to the implementation fails when you extract a helper, rename a private function or swap a library — changes that broke nothing. After a few of those, the team starts deleting tests to get the build green, and the suite has become an obstacle.

two tests of the same function
// implementation: breaks when you rename anything, proves nothing about the result
it("calls formatCurrency once per line", () => {
  const format = spy();
  render(order, { format });

  expect(format.callCount).toBe(2);
});

// behaviour: survives any rewrite that keeps the output right
it("shows each line with a thousands separator", () => {
  const html = render(order);

  expect(html).toContain("1,780");
});

The public surface is the contract

Test a module through the things other code is allowed to call. If a private helper is complicated enough to want its own test, that is a signal it should be its own module with its own public surface — not a reason to reach inside.

The same applies to a component: the surface is the props in and the rendered result out, not the state inside it.

the same rule, three layers
a module    → its exports
a component → props in, rendered output and events out
an API      → request in, status and body out
a page      → what a user can do, and what they then see

The cases worth having

Four or five cases per behaviour is usually right. Twenty variations of the ordinary case cost maintenance and catch nothing extra; the boundaries and the invalid inputs are where the bugs are.

KindExample
The ordinary onetwo doors at the agreed price
The boundaryzero lines, one line, the maximum quantity
The empty and the missing[], null, undefined, ""
The invalida negative quantity, a door that is not in the catalogue
The regressionthe exact input that caused the bug last March

The bug you just fixed

Every bug is a test you did not have. Write it before the fix, watch it fail for the right reason, then fix it. That single habit is the cheapest source of a suite that reflects the real risks of your system rather than somebody's idea of good coverage.

And the things not worth a test

  • Code with no logic: a getter, a re-export, a constant.
  • The framework, the language, the standard library.
  • Exact markup or CSS — assert what a user can find, which is the DOM lesson.
  • Anything where writing the test requires copying the implementation into the test.

Practice