Pattern catalog for "I can't write the test first" moments - recognizes common testability blockers (singletons / static dependencies, network in constructors, time / random as hidden inputs, deeply nested construction, untestable boundaries) and proposes the refactor that unblocks TDD (extract interface, dependency injection, seam, ports-and-adapters). Use as TDD coaching when an engineer is stuck on a class of code. For a catalog of what-to-test heuristics with no story use heuristic-test-design-reference, to label a change's shape before planning test effort use code-change-shape-classifier, and for conventions on writing the test well once the code is testable use test-code-conventions.
80
100%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
TDD's "write the test first" rule breaks against certain code shapes. The engineer hits the wall, abandons TDD, writes the code first, then writes the test second (or skips it). The wall isn't TDD - it's the code shape.
This skill is a catalog of common stuck patterns + the refactor that unsticks each. It's a coaching reference, not a prescription - the engineer picks the appropriate refactor for their codebase.
For TDD basics, defer to Kent Beck's Test-Driven Development by Example (the canonical reference). This skill addresses the second-order problem: the code resists TDD.
test-code-conventions for writing the test well.Each pattern pairs a testability blocker with the refactor that opens a seam. Full before/after code for every pattern lives in the linked references file.
| # | Stuck pattern | Refactor | Full example |
|---|---|---|---|
| 1 | Singleton / static dependency | Dependency injection | references/injection-patterns.md |
| 2 | Network (or other I/O) in constructor | Push side effects out of construction | references/injection-patterns.md |
| 3 | Time / random as hidden input | Inject the source (now, rand, Clock) | references/injection-patterns.md |
| 4 | Untestable boundaries (file system, OS) | Ports-and-adapters (hexagonal) | references/boundary-adapter-patterns.md |
| 5 | Deeply nested construction | Factory + composition root | references/structural-patterns.md |
| 6 | Untestable private methods | Test through the public interface, or extract a class | references/structural-patterns.md |
| 7 | Async / Promise-heavy code | Split into orchestrator + injected steps | references/structural-patterns.md |
| 8 | Code that calls third-party SDKs | Adapter (don't mock what you don't own) | references/boundary-adapter-patterns.md |
Is your test setup more than 10 lines? → Pattern 5 (composition root)
Is your test using `await fetch` / network? → Pattern 4 (port/adapter) or Pattern 8 (gateway adapter)
Does your code call `Date.now()` / `Math.random()`? → Pattern 3 (inject)
Are you wanting to mock a singleton? → Pattern 1 (DI)
Constructor does I/O? → Pattern 2 (push out)
Async chain with 5+ awaits? → Pattern 7 (orchestrator)
Want to test private methods? → Pattern 6 (extract or test through public)An engineer is stuck on processOrder, which reads its data through a
global singleton:
function processOrder(orderId) {
const order = Database.getInstance().findOrder(orderId);
// ...
}db parameter so the caller
supplies the dependency.Database.getInstance() to the composition root, the single
place that passes it in production.processOrder(1, { findOrder: () => ({ id: 1 }) }).Result: the test runs with no global state touched, and the "I can't write the test first" wall is gone - the blocker was the code shape, not TDD.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Skipping the test first because "this is hard to test" | Code stays untestable; debt compounds. | Apply one of the patterns. |
| Reflection to access private methods | Couples tests to implementation; refactors break. | Test through public interface (Pattern 6). |
| Mocking 5 globals to test one function | Brittle; tests verify mocks, not behavior. | DI + factory (Patterns 1, 5). |
| "We'll refactor later" | Later never comes. | Apply pattern incrementally - one method at a time. |
| Big-bang refactor for testability | Risky; tests break for unrelated reasons. | Strangler fig - incrementally extract; test new code; old code unchanged. |
test-code-conventions - §5 covers the test-double taxonomy this skill references.