npx claudepluginhub codagent-ai/agent-skills --plugin codagentThis skill uses the workspace's default tool permissions.
Write the test first. Watch it fail. Write minimal code to pass.
Enforces strict Test-Driven Development (TDD): write failing test first for features, bug fixes, refactors before any production code.
Enforces strict Test-Driven Development for features, bugfixes, refactors: write failing test first, verify fail, minimal code to pass, refactor. Red-Green-Refactor cycle.
Enforces strict TDD for features and bugfixes: RED (write minimal failing test), GREEN (minimal passing code), REFACTOR. No production code without failing test first.
Share bugs, ideas, or general feedback.
Write the test first. Watch it fail. Write minimal code to pass.
Core principle: Watch the test fail. Only then is it proven to test the right thing.
Violating the letter of the rules is violating the spirit of the rules.
Always:
Exceptions (skip TDD only for these; otherwise follow the full TDD cycle):
Thinking "skip TDD just this once"? Stop. That's rationalization.
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
Write code before the test? Delete it. Start over.
No exceptions:
Implement fresh from tests. Period.
Default to unit tests. Escalate only when the thing you're testing can't be isolated:
When in doubt: can you test it with a function call and an assertion? Unit test. Does it require spinning up real infrastructure or connecting real components? Integration test.
Each scenario in the spec becomes one test. Write the test name and assertion from the scenario before writing any implementation.
Spec scenario:
WHEN a user with an expired session requests a protected resource THEN they receive a 401 and a redirect to login
Test:
test('expired session returns 401 with login redirect', async () => {
const session = createExpiredSession();
const response = await requestProtectedResource(session);
expect(response.status).toBe(401);
expect(response.headers.location).toBe('/login');
});
One test per scenario, one scenario per behavior. If a scenario needs multiple assertions, that's fine — but if it needs multiple setups, split it. If the spec numbers its criteria (AC-1, R-001, etc.), reference the ID in the test name or comment for traceability. (Note: the spec skill does not generate IDs by default — IDs are an optional custom extension you may add to your spec for traceability.)
digraph tdd_cycle {
rankdir=LR;
red [label="RED\nWrite failing test", shape=box, style=filled, fillcolor="#ffcccc"];
verify_red [label="Verify fails\ncorrectly", shape=diamond];
green [label="GREEN\nMinimal code", shape=box, style=filled, fillcolor="#ccffcc"];
verify_green [label="Verify passes\nAll green", shape=diamond];
refactor [label="REFACTOR\nClean up", shape=box, style=filled, fillcolor="#ccccff"];
next [label="Next", shape=ellipse];
red -> verify_red;
verify_red -> green [label="yes"];
verify_red -> red [label="wrong\nfailure"];
green -> verify_green;
verify_green -> refactor [label="yes"];
verify_green -> green [label="no"];
refactor -> verify_green [label="stay\ngreen"];
verify_green -> next;
next -> red;
}
Write one minimal test showing what should happen.
Good:
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 thing
Bad:
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 mock not code
Requirements:
MANDATORY. Never skip.
npm test path/to/test.test.ts
Confirm:
Test passes? You're testing existing behavior. Fix test.
Test errors? Fix error, re-run until it fails correctly.
Write simplest code to pass the test.
Good:
async function retryOperation<T>(fn: () => Promise<T>): Promise<T> {
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
Bad:
async function retryOperation<T>(
fn: () => Promise<T>,
options?: {
maxRetries?: number;
backoff?: 'linear' | 'exponential';
onRetry?: (attempt: number) => void;
}
): Promise<T> {
// YAGNI
}
Over-engineered
Don't add features, refactor other code, or "improve" beyond the test. Never add test-only methods to production classes — put test helpers in test utilities.
MANDATORY.
npm test path/to/test.test.ts
Confirm:
Test fails? Fix code, not test.
Other tests fail? Fix now.
After green only:
Keep tests green. Don't add behavior.
Next failing test for next feature.
"I'll write tests after" — Tests written after pass immediately. That proves nothing. Tests-after are biased by your implementation: you test what you built, not what's required. Test-first forces you to see failure, proving the test catches something real.
"Already manually tested" — Ad-hoc, no record, can't re-run, can't prove edge cases. Automated tests are systematic and repeatable.
"Deleting X hours of work is wasteful" — Sunk cost fallacy. Keeping unverified code is the real waste. Delete and rewrite with TDD gives high confidence; bolting tests onto existing code gives false confidence.
"TDD is dogmatic / too slow" — TDD is faster than debugging in production. It finds bugs before commit, prevents regressions, documents behavior, and enables safe refactoring.
"Keep as reference, write tests first" — You'll adapt it. That's testing after with extra steps. Delete means delete.
"Hard to test" — Listen to the test. Hard to test = hard to use. Simplify the design.
If you catch yourself rationalizing, delete code and start over with TDD.
Bug: Empty email accepted
RED
test('rejects empty email', async () => {
const result = await submitForm({ email: '' });
expect(result.error).toBe('Email required');
});
Verify RED
$ npm test
FAIL: expected 'Email required', got undefined
GREEN
function submitForm(data: FormData) {
if (!data.email?.trim()) {
return { error: 'Email required' };
}
// ...
}
Verify GREEN
$ npm test
PASS
REFACTOR Extract validation for multiple fields if needed.
Before marking work complete:
Can't check all boxes? You skipped TDD. Start over.
| Problem | Solution |
|---|---|
| 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. |
| Mock setup > test logic | Understand real method's side effects before mocking. Mock at the lowest level necessary. Consider integration tests instead. |
Production code → test exists and failed first
Otherwise → not TDD