From ctx
Use when implementing any feature or bugfix, before writing production code. Triggers: "tdd", "test first", "test-driven", "write a failing test".
How this skill is triggered — by the user, by Claude, or both
Slash command
/ctx:ctx-tddThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Write the test first. Watch it fail. Write minimal code to pass.
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.
Always:
Exceptions (ask the user):
Thinking "skip TDD just this once"? That's rationalization. See the table below.
Write code before the test? Delete it. Start over.
Implement fresh from tests. Period.
Write one minimal test showing what should happen.
```typescript 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); });
Clear name, tests real behavior, one assertion focus
</Good>
<Bad>
```typescript
test('retry works', async () => {
const mock = jest.fn()
.mockRejectedValueOnce(new Error())
.mockRejectedValueOnce(new Error())
.mockResolvedValueOnce('success');
await retryOperation(mock);
expect(mock).toHaveBeenCalledTimes(3);
});
Vague name, tests the mock not the code
Requirements:
MANDATORY. Never skip.
Run the test. Confirm:
Test passes immediately? You're testing existing behavior. Fix the test.
Test errors? Fix the error, re-run until it fails correctly.
Write the simplest code that makes the test pass.
```typescript async function retryOperation(fn: () => Promise): Promise { for (let i = 0; i < 3; i++) { try { return await fn(); } catch (e) { if (i === 2) throw e; } } throw new Error('unreachable'); } ``` Just enough to pass ```typescript async function retryOperation( fn: () => Promise, options?: { maxRetries?: number; backoff?: 'linear' | 'exponential'; onRetry?: (attempt: number) => void; } ): Promise { /* YAGNI */ } ``` Over-engineered beyond what the test demandsDon't add features, refactor unrelated code, or "improve" beyond the test.
MANDATORY.
Run the test. Confirm:
Test fails? Fix production code, not the test.
Other tests break? Fix those now before moving on.
Keep tests green throughout. Don't add behavior during refactor.
Next failing test for the next behavior.
| Excuse | Reality |
|---|---|
| "Too simple to test" | Simple code breaks. The test takes 30 seconds. |
| "I'll test after" | Tests passing immediately prove nothing. |
| "Tests after achieve the same goals" | Tests-after = "what does this do?" Tests-first = "what should this do?" |
| "Already manually tested" | Ad-hoc is not 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, then 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" shortcuts = debugging in production. |
| "This is different because..." | It's not. |
Any of these mean: delete the code and restart with TDD.
Bug found? Write a failing test that reproduces it first. Then follow the cycle.
RED
test('rejects empty email', async () => {
const result = await submitForm({ email: '' });
expect(result.error).toBe('Email required');
});
Verify RED -- test fails, expected 'Email required', got undefined
GREEN
function submitForm(data: FormData) {
if (!data.email?.trim()) {
return { error: 'Email required' };
}
// ...
}
Verify GREEN -- all tests pass
REFACTOR -- extract validation if needed
Never fix bugs without a failing test first.
| Problem | Solution |
|---|---|
| Don't know how to test | Write the wished-for API. Write the assertion first. Ask the user. |
| Test too complicated | Design too complicated. Simplify the interface. |
| Must mock everything | Code too coupled. Use dependency injection. |
| Test setup is huge | Extract helpers. Still complex? Simplify the design. |
Before claiming work is complete:
Can't check all boxes? You skipped TDD. Start over.
When adding mocks or test utilities, read ${CLAUDE_SKILL_DIR}/references/testing-anti-patterns.md to avoid common pitfalls. The short version: mocks are tools to isolate, not things to test.
SKILL.md -- this file (process, Iron Law, cycle, rationalizations)${CLAUDE_SKILL_DIR}/references/testing-anti-patterns.md -- 5 anti-patterns with gate functions and Good/Bad examplesnpx claudepluginhub garabed96/ctx-plugin --plugin ctxGuides test-driven development: write a failing test first, then minimal code to pass, then refactor. Use before implementing any feature or bugfix to ensure correct behavior.
Enforces test-driven development: write failing test first, then minimal code to pass. Use when implementing features or bugfixes.
Enforces test-driven development workflow: write failing test first, verify failure, implement minimal code, refactor. Use when implementing features or bugfixes.