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
Wrap an external boundary you don't own - the file system, OS calls, or
a third-party SDK - behind an interface you do own, then inject a fake
in tests. Covers stuck Patterns 4 and 8 of tdd-stuck-pattern-resolver.
# Stuck - direct file system access
def load_config():
with open('/etc/myapp/config.json') as f:
return json.load(f)Why it's stuck: test setup requires creating files at fixed paths; tests pollute the filesystem.
Refactor - Hexagonal / Ports-and-Adapters:
# Define a port (interface)
class ConfigSource(Protocol):
def read(self) -> dict: ...
# Production adapter
class FileConfigSource:
def __init__(self, path):
self.path = path
def read(self):
with open(self.path) as f:
return json.load(f)
# Test adapter
class FakeConfigSource:
def __init__(self, config):
self.config = config
def read(self):
return self.config
# Use the port:
def load_config(source: ConfigSource):
return source.read()Tests inject FakeConfigSource({...}); production injects
FileConfigSource('/etc/...').
// Stuck - direct Stripe SDK call
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_KEY);
async function charge(amount) {
return await stripe.paymentIntents.create({ amount, currency: 'usd' });
}Why it's stuck: SDK instances aren't easily mocked; testing without real network is hard.
Refactor - Adapter (don't mock what you don't own):
interface PaymentGateway {
charge(amount: number): Promise<{ id: string }>;
}
class StripeGateway implements PaymentGateway {
constructor(private stripe: Stripe) {}
async charge(amount: number) {
const intent = await this.stripe.paymentIntents.create({ amount, currency: 'usd' });
return { id: intent.id };
}
}
class FakePaymentGateway implements PaymentGateway {
async charge(amount: number) {
return { id: 'fake-charge-' + amount };
}
}Tests use FakePaymentGateway; production uses StripeGateway.
The team owns the interface; mocking is fine - don't mock what
you don't own.