Section 1 · Why and What to Test
What Tests Buy You
10 min3 min readCode playground
Lesson at a glance
Explain what automated tests buy a team, name the four layers of the testing trophy, and pick a sensible layer for a given risk in a small app.
No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.
Last spring a teammate of mine changed one line in a bill-splitting helper: Math.round became Math.floor, to “fix” a share that looked one cent too high. Every screen still worked. Three weeks later a user noticed their group’s balances no longer added up to zero, and nobody could say which of 200 commits had done it. One three-line test would have failed within a second of that change.
That is what tests buy you: the confidence to change code quickly. Without them, every refactor is a gamble and every release needs someone clicking through the app. With good ones, you change the code, run the suite, and know.
What a test is
Strip away the frameworks and a test is code that runs your code and throws when the result is wrong:
function splitEvenly(totalCents, people) {
const base = Math.floor(totalCents / people);
const remainder = totalCents % people;
return Array.from({ length: people }, (_, i) => (i < remainder ? base + 1 : base));
}
const shares = splitEvenly(1000, 3);
const sum = shares.reduce((a, b) => a + b, 0);
if (sum !== 1000) throw new Error(`Expected shares to add up to 1000, got ${sum}`);
console.log('PASS shares add up to the total', shares);Vitest, Jest and Playwright add a lot on top: nice failure messages, watch mode, parallel runs, browsers. Underneath, expect(sum).toBe(1000) is an if that throws.
Tests cost something too
Every test is code you maintain. It takes time to write, CI minutes to run, and attention when it fails. A test that fails when nothing is broken (a flaky or brittle test) is worse than no test: people learn to ignore red builds. So the question is never “how many tests?” but how much confidence per minute of CI each one buys.
The testing trophy
The testing trophy is a popular way to picture the layers and how much of each to write:
- Static analysis (TypeScript, ESLint) catches typos and wrong types before anything runs.
- Unit tests check one function or module in isolation. Fast and precise; ideal for pure logic like money rounding.
- Integration tests check several units working together, such as a form component calling the real parsing code. They catch most real bugs for a modest cost, which is why the trophy is widest here.
- End-to-end tests drive the real app in a real browser. The most realistic and the most expensive; keep them for the flows that would make the news if they broke.
The rule I use daily: pick the cheapest test that would fail if the thing you are worried about broke.
What you build in this course
The running project is Splitwise-lite, a bill-splitting module for a group trip: parse amounts, split bills, add tips, compute who owes whom and settle up, plus a small UI and a network layer. In the playground you get working code and write tests against it. Then the checker runs your tests against deliberately broken copies (a wrong rounding, an off-by-one, swapped arguments) and your tests must catch each one. Passing on good code is half the job; failing on bad code is the other half.
Before installing anything, build the core idea yourself in the exercise below: an assertion that throws and a runner that catches.
Key takeaways
- A test is code that runs your code and throws when the result is wrong; every framework is a nicer wrapper around that idea.
- Tests buy you the confidence to change code quickly; a test that never catches a bug and never documents anything costs more than it earns.
- The testing trophy has four layers (static analysis, unit, integration, end-to-end), with integration tests usually giving the most confidence per minute of CI.
- Choose the cheapest test that would fail if the thing you are worried about broke.