EduFlow
Testing JavaScript Apps
0%

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.

  1. 1What a test is
  2. 2Tests cost something too
  3. 3The testing trophy
  4. 4What you build in this course

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:

JavaScript
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:

A trophy shape with four layers: static analysis at the base, a thin unit layer, a wide integration layer, and a small end-to-end layer on top. End-to-end Integration components + modules together Unit Static: types + lint slower, costlier, more real
The testing trophy: integration tests give the most confidence for their cost
  • 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.

Further reading

Check your understanding

3 questions

Question 1 of 3

Splitwise-lite rounds each person’s share to whole cents. Which test gives you the fastest, most precise signal that the rounding is right?

Question 2 of 3

What does a test framework fundamentally do when an assertion fails?

Question 3 of 3

Your team’s end-to-end suite takes 40 minutes and fails randomly about once a day. Which change most improves confidence per minute of CI?

Practice

Code playground

Build a ten-line test runner

Write the two functions every test framework is built on.

  • expectEqual(actual, expected) does nothing when the values are strictly equal (===) and throws an Error otherwise. The message must include both values, for example Expected 334 but received 333.
  • test(name, fn) calls fn. If it returns normally, log PASS <name>. If it throws, log FAIL <name>: <error message> and do not rethrow, so the next test still runs.

Run it: the second test fails on purpose, because 0.1 + 0.2 is not exactly 0.3 in JavaScript.

// An assertion: throw an Error when actual !== expected.
function expectEqual(actual, expected) {
  // your code
}

// A runner: run fn, log PASS or FAIL, never let one failure stop the others.
function test(name, fn) {
  // your code
}

test("two plus two", () => expectEqual(2 + 2, 4));
test("deliberately wrong", () => expectEqual(0.1 + 0.2, 0.3));
Loading the editor…

Ctrl/⌘ + Enter to run · Runs in a sandbox in your browser. Nothing leaves your device.

Console

(no output)

Checks

  • expectEqual does not throw when the values are equal
  • expectEqual throws an Error that names both values
  • test logs PASS for a passing test
  • test logs FAIL : for a failing test
  • test catches the error instead of rethrowing it