Guide test-driven development through the mandatory Red-Green-Refactor cycle (failing test before code), enforce test quality (one behavior per test, real code over mocks, no implementation-detail testing), and enforce test runner discipline (run mode, no watch mode). Use when implementing features or fixing bugs (with `testing.tddMode='enforce'` blocking implementation without a failing test). This skill MUST be consulted because test-first is the primary quality enforcement point; tests tha...
Scanned 5/27/2026
Install via CLI
openskills install synaptiai/synapti-marketplace---
name: tdd-patterns
description: "Guide test-driven development through the mandatory Red-Green-Refactor cycle (failing test before code), enforce test quality (one behavior per test, real code over mocks, no implementation-detail testing), and enforce test runner discipline (run mode, no watch mode). Use when implementing features or fixing bugs (with `testing.tddMode='enforce'` blocking implementation without a failing test). This skill MUST be consulted because test-first is the primary quality enforcement point; tests that pass on first write are suspect (likely testing the wrong thing)."
allowed-tools: Bash, Read, Write, Edit, Grep, Glob, TaskCreate, TaskList, TaskUpdate
context: fork
agent: general-purpose
paths:
- "**/*.test.{js,jsx,ts,tsx,mjs,cjs}"
- "**/*.spec.{js,jsx,ts,tsx,mjs,cjs}"
- "**/__tests__/**"
- "**/test_*.py"
- "**/*_test.py"
- "**/tests/**.py"
- "**/spec/**.rb"
- "**/*_spec.rb"
- "**/*_test.go"
- "**/test_*.rs"
---
# TDD Patterns
Domain skill for test-driven development: Red-Green-Refactor cycle, test quality, and runner discipline.
## Iron Law
**IF YOU DIDN'T WATCH THE TEST FAIL, YOU DON'T KNOW IF IT TESTS THE RIGHT THING.**
A test that has never failed might pass for the wrong reason. The RED phase exists to prove the test is valid.
## Red-Green-Refactor Cycle
For each feature or fix, track the TDD cycle with tasks:
```
TaskCreate("RED: Write failing test for {behavior}", "Minimal test describing desired behavior. MUST fail on first run.")
TaskCreate("GREEN: Implement {behavior}", "Simplest code to make the test pass. No cleverness.")
TaskCreate("REFACTOR: Clean up {behavior}", "Improve quality. All tests must still pass after each step.")
```
### RED: Write a Failing Test
TaskUpdate(RED task, status: "in_progress")
1. Write the **minimal** test that describes the desired behavior
2. Run it — it MUST fail
3. Verify it fails for the **right reason** (not a syntax error, not wrong import)
TaskUpdate(RED task, status: "completed")
### GREEN: Make It Pass
TaskUpdate(GREEN task, status: "in_progress")
1. Write the **simplest** code that makes the test pass
2. No cleverness. No optimization. No "while I'm here" improvements.
3. Run the test — it MUST pass now
TaskUpdate(GREEN task, status: "completed")
### REFACTOR: Clean Up
TaskUpdate(REFACTOR task, status: "in_progress")
1. Improve code quality (naming, structure, duplication)
2. Run ALL tests after each refactor step — they must still pass
3. No new functionality during refactor — that's a new RED phase
TaskUpdate(REFACTOR task, status: "completed")
Use TaskList to confirm the full cycle completed before moving to the next behavior.
**The cycle is non-negotiable.** RED → GREEN → REFACTOR. Always in this order.
## Test Runner Discipline
**Always use `run` mode, never `watch` mode:**
| Framework | Correct | Wrong |
|-----------|---------|-------|
| Vitest | `vitest run` | `vitest` (watch) |
| Jest | `CI=true jest` or `jest --watchAll=false` | `jest` (watch) |
| Pytest | `pytest` | — |
| RSpec | `rspec` | `guard` (watch) |
Watch mode leaves orphan processes. Verify cleanup:
```bash
pgrep -f "vitest|jest|pytest" && echo "ORPHAN PROCESS — kill it"
```
## Test Quality Standards
### One Behavior Per Test
```
# GOOD: Each test verifies one thing
test "returns empty array when no items match"
test "returns matching items sorted by relevance"
# BAD: Multiple behaviors in one test
test "search works correctly" # What does "correctly" mean?
```
### Descriptive Names
Test names should read as specifications:
- "creates user with valid email"
- "rejects duplicate usernames"
- "returns 404 when resource not found"
### Real Code Over Mocks
- Mock ONLY at external boundaries (APIs, databases, file system)
- Never mock the code under test
- Never mock internal collaborators unless they have side effects
- If you need many mocks, the design needs improvement
### No Testing Implementation Details
- Test behavior (what), not implementation (how)
- Don't assert on internal state, private methods, or call counts
- If refactoring breaks tests but not behavior, the tests are wrong
## When TDD Applies
| Scenario | TDD? | Notes |
|----------|------|-------|
| New feature | **Always** | Define behavior before implementing |
| Bug fix | **Always** | Write reproducing test first |
| Refactor | Existing tests | Ensure tests pass before AND after |
| Prototype/spike | Optional | But write tests before merging |
| Config changes | No | Unless behavior changes |
## Process Enforcement
Check `settings.json` → `testing.tddMode`:
| Mode | Default? | Behavior |
|------|----------|----------|
| `enforce` | **Yes** | Block implementation without a failing test. No exceptions. The RED phase must produce a failing test before any production code is written. |
| `suggest` | No (opt-in) | Recommend TDD. Allow override with explicit user decision. |
| `off` | No (opt-in) | No TDD guidance. Tests still run in verification. |
### Opt-out mechanism
`enforce` is the default because skipping the RED phase is the #1 source of tests that pass for the wrong reason. Teams that need the old `suggest` behavior can opt out:
```json
{
"testing": {
"tddMode": "suggest",
"tddModeOptOut": true
}
}
```
Set `testing.tddModeOptOut` to `true` in `settings.json` to switch `tddMode` back to `suggest`. When `tddModeOptOut` is `false` (the default), `tddMode` must remain `enforce`. This two-field design ensures the opt-out is an explicit, auditable decision rather than a silent default change.
## Coverage Targets
Aim for meaningful coverage, not vanity metrics:
- **80%+** for branches, functions, lines, statements
- **100%** for critical paths (auth, payments, data mutations)
- **0%** is acceptable for generated code, config files, type definitions
## Rationalization Prevention
| Excuse | Response |
|--------|----------|
| "Skip TDD just this once" | Delete the code. Start over. The cycle is the discipline. |
| "This is too simple to test" | Simple code, simple test. Write it in 30 seconds. |
| "I'll write tests after" | You won't. And if you do, they'll test what you wrote, not what you should have written. |
| "Tests slow me down" | Tests slow you down NOW. Bugs slow you down FOREVER. |
| "The test passes on first try" | That's a red flag. It should have failed first. Check: is it testing the right thing? |
## Stop Conditions
| Trigger | Action |
|---------|--------|
| Test passes immediately on first write | Test is suspect. Verify it fails when you break the code intentionally. |
| >3 tests needed for one function | Function may be doing too much. Consider splitting. |
| Mocking >2 dependencies | Design smell. Refactor to reduce coupling. |
| Test is harder to write than the code | Step back. Either the interface is wrong or the test is testing implementation. |
No comments yet. Be the first to comment!