supersam/docs/superpowers/skills/test-driven-development/SKILL.md

3.8 KiB

name description
test-driven-development Use when implementing any feature or bugfix, before writing implementation code

Test-Driven Development (TDD)

Overview

Write the test first. Watch it fail. Write minimal code to pass.

Core principle: If you didn't watch the test fail, you don't know if it tests the right thing.

When to Use

Always:

  • New features
  • Bug fixes
  • Refactoring
  • Behavior changes

Exceptions (ask your human partner):

  • Throwaway prototypes
  • Generated code
  • Configuration files

The Iron Law

NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST

Write code before the test? Delete it. Start over.

Red-Green-Refactor

RED — Write Failing Test

Write one minimal test showing what should happen.

test('retries failed operations 3 times', async () => {
  let attempts = 0;
  const operation = () => {
    attempts++;
    if (attempts < 3) throw new Error('fail');
    return 'success';
  };

  const result = await retryOperation(operation);
  expect(result).toBe('success');
  expect(attempts).toBe(3);
});

Requirements:

  • One behavior
  • Clear name
  • Real code (no mocks unless unavoidable)

Verify RED — Watch It Fail

MANDATORY. Never skip.

npm test -- path/to/test.test.js

Confirm:

  • Test fails (not errors)
  • Failure message is expected
  • Fails because feature missing (not typos)

Test passes? You're testing existing behavior. Fix test. Test errors? Fix error, re-run until it fails correctly.

GREEN — Minimal Code

Write simplest code to pass the test. Don't add features, refactor other code, or "improve" beyond the test.

Verify GREEN — Watch It Pass

MANDATORY.

npm test -- path/to/test.test.js

Confirm:

  • Test passes
  • Other tests still pass
  • Output pristine (no errors, warnings)

Test fails? Fix code, not test. Other tests fail? Fix now.

REFACTOR — Clean Up

After green only:

  • Remove duplication
  • Improve names
  • Extract helpers

Keep tests green. Don't add behavior.

Repeat

Next failing test for next feature.

This Project's Test Conventions

  • Test files live alongside source: src/services/orderService.test.js
  • Test runner: Vitest (npm run test)
  • Tests are .test.js files, co-located with the module they test
  • Existing tested modules: orderService.js, deliveryInvitationApi.js, driverDeliveries.js, orderViews.js
  • Mock data available in src/data/mockAppData.js for test fixtures

Common Rationalizations

Excuse Reality
"Too simple to test" Simple code breaks. Test takes 30 seconds.
"I'll test after" Tests passing immediately prove nothing.
"Already manually tested" Ad-hoc ≠ systematic. No record, can't re-run.
"Deleting X hours is wasteful" Sunk cost fallacy. Keeping unverified code is technical debt.
"TDD is dogmatic, I'm being pragmatic" TDD IS pragmatic: finds bugs before commit, prevents regressions.

Red Flags — STOP and Start Over

  • Code before test
  • Test passes immediately
  • Test after implementation
  • "I already manually tested it"
  • "This is different because..."

All of these mean: Delete code. Start over with TDD.

Verification Checklist

Before marking work complete:

  • Every new function/method has a test
  • Watched each test fail before implementing
  • Each test failed for expected reason (feature missing, not typo)
  • Wrote minimal code to pass each test
  • All tests pass
  • Output pristine (no errors, warnings)
  • Tests use real code (mocks only if unavoidable)
  • Edge cases and errors covered

Can't check all boxes? You skipped TDD. Start over.

Final Rule

Production code → test exists and failed first
Otherwise → not TDD

No exceptions without your human partner's permission.