Skip to content
Back to skills

Test Driven Development

ASecurity

Use when implementing any feature or bugfix, before writing implementation code

  • 10 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 1, 2026
ai-agentsgobashtestingdebuggingrefactoringapi

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned September 1, 2026

npx -y skills add dork-labs/dorkos --skill test-driven-development --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Test Driven Development?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Test Driven Development
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/dork-labs-test-driven-development/badge)](https://www.skillsdirectory.com/skills/dork-labs-test-driven-development)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: test-driven-development
description: 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.

**Violating the letter of the rules is violating the spirit of the rules.**

## When to Use

**Always:** new features, bug fixes, refactoring, behavior changes.

**Exceptions (ask the user):** throwaway prototypes, generated code, configuration files.

Thinking "skip TDD just this once"? Stop. That's rationalization.

## The Iron Law

```
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
```

Write code before the test? Delete it. Start over. Don't keep it as "reference", don't "adapt" it while writing tests — delete means delete. Implement fresh from tests.

## Red-Green-Refactor

### RED — Write Failing Test

One minimal test showing what should happen: one behavior, a name that describes it, real code under test (mocks only if unavoidable — a test that only exercises `vi.fn()` chains tests the mock, not the code).

### Verify RED — Watch It Fail

**MANDATORY. Never skip.**

```bash
pnpm vitest run path/to/test.test.ts
```

Confirm:

- Test **fails** (not errors)
- Failure message is the one you expected
- It fails because the feature is missing (not a typo or bad import)

**Test passes?** You're testing existing behavior — fix the test.
**Test errors?** Fix the error and re-run until it fails correctly.

### GREEN — Minimal Code

Write the simplest code that passes the test. No extra options, no speculative parameters, no refactoring other code, no "improvements" beyond what the test demands (YAGNI).

### Verify GREEN — Watch It Pass

**MANDATORY.**

```bash
pnpm vitest run path/to/test.test.ts
```

Confirm: the test passes, other tests still pass, output is pristine (no errors or warnings).

**Test fails?** Fix the code, not the 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. Then repeat with the next failing test.

## Why Test-First (Not Test-After)

Tests written after code pass immediately — and passing immediately proves nothing: they're biased by your implementation, test what you built rather than what's required, and you never saw them catch anything. Test-first forces you to see the failure, which is the proof the test works. Manual testing doesn't substitute: no record, can't re-run, edge cases forgotten under pressure.

## 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 does not equal systematic. No record, can't re-run.    |
| "Deleting X hours is wasteful"         | Sunk cost fallacy. Keeping unverified code is technical debt. |
| "Keep as reference, write tests first" | You'll adapt it. That's testing after. Delete means delete.   |
| "Need to explore first"                | Fine. Throw away exploration, start with TDD.                 |
| "Test hard = design unclear"           | Listen to the test. Hard to test = hard to use.               |
| "TDD will slow me down"                | TDD is faster than debugging. Pragmatic = test-first.         |
| "Existing code has no tests"           | You're improving it. Add tests for existing code.             |

**Red flags — stop and start over:** code before test, test passes immediately, can't explain why the test failed, "just this once", "it's about spirit not ritual", "this is different because...".

## DorkOS-Specific Patterns

Full conventions: `.claude/rules/testing.md`. The essentials:

- **Location**: tests live in `__tests__/` directories alongside source (e.g. `apps/server/src/services/__tests__/foo.test.ts`)
- **Client components**: need the `@vitest-environment jsdom` directive and a mock `Transport` via `TransportProvider` + `createMockTransport()` from `@dorkos/test-utils`
- **Server session routes**: use `FakeAgentRuntime` from `@dorkos/test-utils`, never hand-rolled runtime mocks
- **SSE streams**: use `collectDurableEvents` from `@dorkos/test-utils` (trigger the turn first — message POSTs are trigger-only 202s)

### Commands

```bash
pnpm vitest run path/to/test.test.ts   # one file — the TDD inner loop
pnpm test -- --run                     # full suite (via turbo)
```

**Gotcha:** never run bare `pnpm vitest run` for a full run — in the dev environment it falsely fails 2 tests. Full runs go through `pnpm test -- --run`; bare `pnpm vitest run` is only for scoped paths.

## Verification Checklist

Before marking work complete:

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

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

## When Stuck

| Problem                | Solution                                                   |
| ---------------------- | ---------------------------------------------------------- |
| Don't know how to test | Write wished-for API. Write assertion first. Ask the user. |
| Test too complicated   | Design too complicated. Simplify interface.                |
| Must mock everything   | Code too coupled. Use dependency injection.                |
| Test setup huge        | Extract helpers. Still complex? Simplify design.           |

## Debugging Integration

Bug found? Write a failing test reproducing it, then follow the TDD cycle. The test proves the fix and prevents regression. Never fix bugs without a test.

## Testing Anti-Patterns

When adding mocks or test utilities, read @testing-anti-patterns.md to avoid common pitfalls: testing mock behavior instead of real behavior, adding test-only methods to production classes, mocking without understanding dependencies.

## Final Rule

```
Production code -> test exists and failed first
Otherwise -> not TDD
```

No exceptions without the user's permission.

Files in this skill

  • SKILL.md6.5 KB
  • testing-anti-patterns.md9.1 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…