Why write tests
Tests don't prove your code works. They're what lets you change it six months from now without breaking something you can't see happen.

Sam joins a team in March. In April, a ticket asks for a small change: bump the free-tier link limit from 50 to 100. Sam finds the line, changes 50 to 100, opens a pull request, and merges it after a quick look from a teammate. Two days later, support is fielding tickets from users whose accounts are suddenly locked out. It turns out that same number was reused three other places, none of which Sam knew existed, and one of them decided whether a session token was still valid. Nobody had told Sam. Nothing had told Sam. There was no test that would have failed and said "this number means something else over here too."
That's the story behind almost every "how did this break" postmortem. Not carelessness. A change that looked isolated and wasn't, with nothing in the codebase to say so.
Tests don't prove your code works
Here's the thing people get backwards: a green test suite is not a certificate of correctness. You can write a function with a real bug, write a test that happens to call it in a way that never hits the bug, and watch the suite pass. We'll spend a whole lesson on that trap later in this series. So if tests aren't proof, what are they for?
They're there so you can change code later without holding your breath.
That's the actual argument. Not "tests catch bugs" (they catch some, and miss plenty). Not "tests document behavior" (true, but a side effect). The core reason a working engineer writes tests is that code changes constantly, and every change is a chance to break something that used to work. A test suite is the thing that tells you, in seconds, whether your change broke something else. Without it, you find out from a user, or from a 2 a.m. page.
What it feels like without a safety net
Watch someone work in a codebase with no tests and you'll see the same patterns:
- They copy-paste a function instead of extracting a shared helper, because touching the original feels too risky.
- They avoid renaming things, because there's no fast way to check they got every call site.
- They ship on a Friday and quietly watch the error logs over the weekend.
- A "quick fix" takes three hours, most of it spent manually clicking through the app to confirm nothing else broke.
None of that is laziness. It's a rational response to not knowing what depends on what. The codebase has become something people are afraid to touch, and a codebase people are afraid to touch slowly stops improving. Bugs don't get fixed because fixing them is scary. Old patterns don't get cleaned up because nobody wants to be the one who broke prod refactoring a "simple" thing.
What a suite actually buys you
Three concrete things, in order of how often you'll feel them:
A regression alarm. You change intParam to support a new query parameter, run the suite, and eleven tests fail. Every one of them is telling you exactly what you broke, before a user does. This is the big one. It turns "did I break something?" from a guess into an answer that takes ten seconds.
Permission to refactor. Once behavior is pinned down by tests, you can rewrite the internals however you like. Split a function, rename variables, swap a loop for .map(). If the tests still pass, the outside behavior is unchanged, and that's the only thing that mattered.
A spec that can't drift. Comments go stale. A test named "rejects invalid input with 400 and a field list" either passes or it doesn't. It stays true or it gets fixed, which is a much stronger guarantee than a comment above a function nobody reads.
Both loops end with the next change. The difference is whether you find out something's wrong from a red test in ten seconds, or from a support ticket in ten hours.
What this series builds
We're going to write a lot of small, real tests, starting with a single assertion you can run right in your browser in the next lesson. Then we'll cover the shape a good test takes, the red-green-refactor loop, the edge cases that actually ship as bugs, mocking the things you don't control, and async code that flakes for reasons that have nothing to do with your logic.
Partway through, we switch from testing plain functions to testing a real API. The app is Linkstash, a small bookmark-saving service built with Express, covered in the Backend & APIs series. It has real routes, real authentication, and a real database, which means the tests we write against it look like tests you'd write on the job, not toy examples. If you've ever used Git to undo a change you regretted, you already understand the appeal here: a test suite is the same kind of insurance, just for behavior instead of file history, as the Git series covers for version history.
By the end, you'll have a suite wired into a CI pipeline that runs on every push, so "the tests pass" stops being something one person checks locally and becomes something the whole team can trust.
Try this before the next lesson
Pick any function you've written recently, even a small one. Ask yourself: if I changed this tomorrow, how would I know I didn't break it? If the honest answer is "I'd have to test it by hand," that's the gap this series closes.
Next up: your first unit test with Vitest, where you'll write and run a real assertion in about two minutes.

Written by
Rhythm Bhiwani
Engineer and relentless builder, happiest reverse-engineering hard problems until they click.
Enjoyed this?
Tap the heart to leave some love.
Be the first to react
Comments
Join the conversation.
Loading comments…


