Skip to main content
CodeOath
← All posts

Testing64 min total · 12 parts

Testing Fundamentals: Unit, Integration, and E2E Tests Done Right

Part 1 of 12 · ~3 min

Overview

Nearly everyone's first real exposure to testing runs backwards. You find a describe block someone else wrote, imitate its shape until the runner stops complaining, and ship it — without ever pausing to ask what would have made that particular test worth keeping. That gap doesn't stay theoretical for long. Months later it shows up as a suite that goes red on a harmless rename nobody asked it to care about, mocks stacked three deep until the thing under test isn't really running anymore, and a coverage badge everyone points to with pride while a real defect sits untouched underneath it, invisible to every check that ran.

So we are going to do it in the other order. One system, built and tested together, from here to the last paragraph.

Here is the system: a timed take-home coding assessment. A recruiter picks an exercise and a window of a few days. The candidate gets an emailed link. When they open it, a ninety-minute clock starts. They write code in a browser editor, hit submit, and a separate service runs their code against the exercise's graded cases and mails the recruiter a score.

It is a small product and a deeply unglamorous one, which is exactly why it works here. It has a pure function at its heart, a database behind it, a second service in a second language next to it, a clock it cannot escape, a UI on top of it, and a user journey through all of that — which means every single idea in this reference has a genuine, non-contrived home in it. No toy carts, no foo and bar.

One note on vocabulary before we start, because this topic has a collision built into it. Our product is about assessments, and an assessment is a kind of test. To keep that from getting confusing, "test" in this reference always means one of our own automated tests. What the candidate takes is an assessment; it contains an exercise; the grader runs the exercise's cases. That distinction holds for the rest of this reference.

Here is the piece we start from. It is the rule that decides whether a candidate is allowed to work right now, and almost everything about it is subtly wrong:

// lib/invites.js — version 1
function inviteStatus(invite) {
  const now = Date.now();
  if (invite.submittedAt) return "submitted";
  if (now < invite.opensAt) return "early";
  if (now > invite.closesAt) return "expired";
  return "open";
}

Seven lines, and support already has tickets about it. By the end of this reference it will take its clock as an argument instead of reaching out and grabbing one, it will know about the candidate's own ninety minutes and not just the recruiter's window, it will have a test suite that actually fails when it is wrong, and it will be surrounded by tests at three different layers that each catch a different kind of bug. Every chapter closes one part of the gap between those two versions, and the mechanism that closes it is that chapter's real subject.

Work through it in order if you can — the system genuinely accumulates, and later chapters go back and collect bugs earlier ones left sitting there on purpose. If you know what you came for, the sidebar will drop you straight into it; each chapter says which part of the system it is working on, so you can pick up the thread from anywhere.