ByeBuy.ai
BUILD YOUR ESCAPE ROUTE · ✦ CURSOR · HOST IT · ◫ SUPABASE · CONNECT IT · ↯ RELAY · BUILD YOUR ESCAPE ROUTE · ✦ CURSOR · HOST IT · ◫ SUPABASE · CONNECT IT · ↯ RELAY ·
CURRICULUM
← BYEBUY NOTES

September 12, 2026

UNIT AND COMPONENT TESTS: CHECK SMALL RULES WITHOUT PRETENDING THEY ARE THE WHOLE APP

Unit and Component Tests: Check Small Rules Without Pretending They Are the Whole App

Lesson 50.1 gave you the grammar — Given/When/Then — and the portfolio. Now start at the bottom of the portfolio, where tests are cheapest: the small, fast checks. Used well, they pinpoint a broken rule in seconds. Used badly, they pile up by the hundred and prove nothing.

Two small definitions

  • A unit test is a focused check of one small function or rule: normalize a ticker, format a citation, decide whether an action is allowed.
  • A component test is a check that one interface unit renders and behaves correctly with controlled inputs: given these props and this signed-in state, the watchlist button shows the right label and fires the right callback.

Both run without the network, the database, or a browser. That is their power — milliseconds per test, exact failure location — and their limit. They never prove the route, the policy, and the database agree with each other. That proof belongs to Lesson 50.3.

Examples you can picture

Research Desk has a handful of perfect small-rule candidates:

  • Ticker normalization. normalizeTicker(" aapl ") returns AAPL. Pure function, three behaviors: trims, uppercases, rejects empties.
  • Citation formatter. Given a brief with a source missing its date, the formatter renders "source, date unavailable" instead of crashing or printing undefined.
  • Watchlist button. Given a signed-out visitor, the button shows "Sign in to save" and routes to sign-in rather than calling the save API. Given a signed-in user who already saved AAPL, it shows "Saved" and disables the duplicate call.
  • Policy function. canEditWatchlistItem(requestingUserId, item) returns true for the owner, false for anyone else, false for anonymous — no database involved, just the rule.

Neighborhood Events has equivalents: a date validator that rejects "February 30th," an event card that renders an honest empty slot list. Small rules, exact assertions.

Anatomy of a valuable test

A test earns its keep when it has five qualities:

1. A meaningful assertion. It checks observable behavior, not that code ran. "Expect the watchlist to contain one item owned by User A" beats "expect the function to return truthy." 2. A behavior-revealing name. The name states the rule: denies watchlist delete to non-owner. If the name only names the function — testCanEdit — it is not evidence yet. 3. Controlled setup. Fixed inputs, fixed users, fixed time. No calls to a live provider, no dependence on today's date or a random ID unless randomness is the behavior under test. 4. One clear reason to fail. When it goes red, you know which rule broke. A test that sets up five behaviors and asserts all of them is five tests wearing a trench coat. 5. No unrelated dependencies. It does not need the database, the network, or three other services to answer a question about one rule.

A healthy small test reads almost like the Given/When/Then sentence from Lesson 50.1:

test("denies watchlist delete to a different signed-in user", () => {
  // Given an item owned by User A...
  const item = { id: "w1", ownerId: "user-A", ticker: "AAPL" };
  // When User B attempts to delete it...
  const result = canDeleteWatchlistItem("user-B", item);
  // Then the action is denied.
  expect(result.allowed).toBe(false);
});

Brittle tests: how AI fills your repo with confetti

Ask an AI "add tests for this file" with no guidance and you will get plenty of tests — and most will be brittle: they break on harmless changes or pass despite real bugs. Watch for three species:

  • Implementation-detail tests. The test asserts *how* the code works instead of *what* it guarantees: internal variable names, call order of private helpers, exact CSS class strings. Rename a variable and the test fails; break the user-visible rule and it passes. Test the contract (props in, rendered behavior out), not the internals.
  • Snapshots nobody reads. A snapshot dumps a whole rendered tree to a file; the next run compares against it. The first time the AI regenerates the snapshot to "fix" the failure, the test becomes a rubber stamp for whatever the code happens to do today. Snapshots are fine for catching accidental churn if someone actually reviews the diff. Unreviewed, they are decoration.
  • Self-confirming mocks. The test replaces a collaborator with a mock that simply repeats the code's own assumption — the failure story Lesson 50.4 dissects, where authorization is mocked to always return true. The test then proves the mock agrees with itself. Mocks should simulate *controlled inputs*, never the very rule under test.

Count is not quality. Twenty behavior-named tests that each fail for exactly one real bug beat two hundred auto-generated assertions that pass no matter what the user experiences.

The name must explain the rule

Make this your review habit: read the test names aloud, with the implementation hidden. Can you reconstruct the product rules? Compare:

  • Weak: test button, test save 2, watchlist snapshot matches.
  • Strong: shows sign-in prompt instead of saving when visitor is signed out, rejects tickers longer than 10 characters with a customer-safe error, denies watchlist delete to non-owner.

If the name cannot explain the user or system rule, the test is not evidence yet. Rename it or rewrite the assertion until it is.

Exercise: propose three, then interrogate them

Take one changed function — normalizeTicker or canDeleteWatchlistItem — and prompt your AI like this: "Propose exactly three tests for this function. Name each after the behavior it checks, and for each, state what bug would make it fail."

Then review the proposal with a red pen:

1. For each test, invent the bug it should catch and check mentally: would the test actually go red? 2. Flag any test that depends on the network, the clock, or another service without needing to. 3. Flag any mock that reimplements the rule instead of supplying inputs.

Finish line: a small test file with behavior-based names plus a two-line note at the top stating what the file deliberately does not cover (for example: "Does not prove the API route enforces this policy; see the boundary test in Lesson 50.3."). When you submit it, put the file up for review like any production change — the GitHub pull-request review flow applies to test diffs too, because a bad test is a bad gate.

Verify fast: change the implementation to be wrong on purpose — return true for every user — and run the file. Every policy test should fail. Common failure: all tests still pass, which means the mocks encoded the same wrong assumption as the code.

Check your understanding

1. What is the difference between a unit test and a component test? 2. Name two qualities of a valuable test and one species of brittle test. 3. Why is "testSaveWorks" a bad test name? Rewrite it as a behavior name. 4. What should you write down that a focused test file deliberately does not prove?

Next

Small rules, checked fast. But the expensive failures live where pieces meet — the route, the authorization check, and the database policy. Lesson 50.3 crosses those seams with integration and browser tests, the proofs that actually catch the one-line permission disaster.

ARTICLE DISCUSSION

JOIN THE
CONVERSATION.

0 COMMENTS

BYEBUY ACCOUNT ACCESS

Sign in

Use your account to save routes and make the catalogue yours.

Enter your email and we’ll send a secure sign-in link and code.

NEW ROUTES ADDED WEEKLY · 9,235 CATALOGUE ENTRIES · BUILD · DEPLOY · QUERY · STACK · SAY BYE TO BUY · NEW ROUTES ADDED WEEKLY · 9,235 CATALOGUE ENTRIES · BUILD · DEPLOY · QUERY · STACK · SAY BYE TO BUY ·