~/VibeHandbook
$39

Chapter 15 · 02

Tests: your safety net

A test is code that checks other code does what you expect. Once written, it runs in seconds and tells you immediately if something regressed.

A healthy test suite looks like a pyramid: lots of tiny fast unit tests at the base, fewer integration tests in the middle, and only a handful of slow end-to-end tests at the top. Most of your safety comes cheaply from the bottom:

              ╱╲
             ╱  ╲        E2E         few · slow · whole app
            ╱────╲       (end-to-end)
           ╱      ╲
          ╱  INTEG ╲     INTEGRATION  some · medium
         ╱──────────╲    (parts together)
        ╱            ╲
       ╱     UNIT     ╲  UNIT          many · fast · one function
      ╱────────────────╲
         cheap base = most of your tests

You don't need 100% coverage. Start by testing the things that would hurt most if they broke: login, payments, data that can't be regenerated, core business logic. The AI is excellent at writing tests — often better than at writing features, because the expected behavior is concrete.

Here's a that works well:

Write tests for the calculateDiscount function in src/pricing.js.
Cover: a normal case, the zero/empty case, the maximum-discount
boundary, and one invalid input. Use the existing test setup in
this repo (check how other *.test.js files are structured).
Run the tests and show me they pass before you finish.

That last line matters. Always ask the AI to run the tests, not just write them. A test that was never executed is a guess. It's also worth being specific about which cases to cover, because left to itself the AI tends to write three variations of the happy path and call it thorough. The cases that actually catch bugs are the boring ones: empty input, the boundary value, the thing that should throw.

A simple example of what it should produce:

import { calculateDiscount } from "./pricing.js";
import { describe, it, expect } from "vitest";

describe("calculateDiscount", () => {
  it("applies a normal percentage discount", () => {
    expect(calculateDiscount(100, 0.2)).toBe(80);
  });

  it("returns the full price when discount is zero", () => {
    expect(calculateDiscount(100, 0)).toBe(100);
  });

  it("never discounts below zero", () => {
    expect(calculateDiscount(100, 1.5)).toBe(0);
  });

  it("rejects a negative price", () => {
    expect(() => calculateDiscount(-10, 0.2)).toThrow();
  });
});

When you change a feature later, run the tests first. If they go red, you found the break before your users did. When the AI fixes a bug, ask it to add a test that fails on the old behavior — that way the bug can't quietly come back. This habit has a name: the regression test. Every bug you hit is a lesson the codebase paid for; a regression test is how you keep the lesson.

One warning: a test can be wrong too. If the AI writes a test that asserts the buggy behavior is correct, it will pass forever while your app stays broken. So read the assertions, not just the green checkmarks. A passing test only means "the code does what the test says" — you still have to confirm the test says the right thing.

Want it offline?

Get the PDF + EPUB + downloadable prompt library + version updates.

$ Get the PDF — $39