Design code for testability using boundary isolation, dependency injection, and testable architecture patterns. Use when designing test strategies, writing unit tests, improving test coverage, refactoring untestable or hard-to-test code, working with mocking or test doubles, or applying TDD practices.
87%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
Architecture and design patterns for testable code, focusing on boundary isolation and dependency injection.
If code is hard to test, it's a design problem, not a testing problem.
Test at architectural boundaries, not internal implementation details:
Each layer should be testable in isolation:
Output: List of testability blockers.
Example:
Testability blocker: OrderService instantiates PostgresRepository in constructor.
Problem: Cannot unit test OrderService without a real database.
Refactor: Inject IOrderRepository interface; provide mock in tests.Output: Dependencies injected via constructor or method parameters.
Template:
// Before: hard to test (concrete dependency)
class OrderService {
private repo = new PostgresOrderRepository()
async createOrder(input: OrderInput): Promise<Order> {
// Logic using this.repo
}
}
// After: testable (injected interface)
class OrderService {
constructor(private repo: IOrderRepository) {}
async createOrder(input: OrderInput): Promise<Order> {
// Logic using this.repo (mockable in tests)
}
}Output: Pure business logic separated from side effects.
Use the Humble Object pattern:
Example:
// Humble object (minimal logic, hard to test)
class HttpController {
constructor(private useCase: CreateOrderUseCase) {}
async handle(req: Request, res: Response): Promise<void> {
const input = { userId: req.body.userId, items: req.body.items }
const output = await this.useCase.execute(input)
res.json(output)
}
}
// Testable object (pure logic, easy to test)
class CreateOrderUseCase {
constructor(private repo: IOrderRepository) {}
async execute(input: CreateOrderInput): Promise<CreateOrderOutput> {
// Business logic here (unit testable with mock repo)
}
}Output: Mocks, stubs, or fakes for dependencies.
Choose appropriate test double:
Example:
// Stub (for queries)
class StubOrderRepository implements IOrderRepository {
async findById(id: string): Promise<Order | null> {
return new Order({ id, status: 'pending' })
}
}
// Mock (for commands)
class MockOrderRepository implements IOrderRepository {
saveWasCalled = false
async save(order: Order): Promise<void> {
this.saveWasCalled = true
}
}
// Fake (lightweight alternative)
class InMemoryOrderRepository implements IOrderRepository {
private orders = new Map<string, Order>()
async save(order: Order): Promise<void> {
this.orders.set(order.id, order)
}
async findById(id: string): Promise<Order | null> {
return this.orders.get(id) || null
}
}Output: Test coverage at each architectural boundary.
Check:
Example:
Unit test: CreateOrderUseCase with mock IOrderRepository
Integration test: PostgresOrderRepository with real database
End-to-end test: POST /orders with real HTTP server and databaseWHY: A hard-wired dependency cannot be replaced in a test, so the class needs real infrastructure to run.
BAD: new PostgresRepository() in the OrderService constructor.
GOOD: Depend on the IRepository interface; inject the implementation.
WHY: Tests coupled to internal calls break on every refactor while saying nothing about behaviour.
BAD: Test that method X calls method Y internally. GOOD: Test public behaviour: given input, expect output.
WHY: Real databases and networks make unit tests slow, flaky and unable to pinpoint a failure.
BAD: Unit test connects to a real database. GOOD: Unit test uses a mock repository; integration test uses the real database.
WHY: Hard-to-test code is a design signal; skipping the test hides the coupling and ships untested code.
BAD: Skip the test, ship untested code. GOOD: Refactor the code to be testable (dependency injection, layer isolation).
WHY: Mixed logic and infrastructure forces every business rule test to set up SQL, HTTP or filesystems.
BAD: OrderService contains SQL queries and HTTP response formatting.
GOOD: OrderService orchestrates pure entities; adapters handle infrastructure.
WHY: Statics and singletons are hidden dependencies that need static-mocking tools and shared-state resets in every test.
BAD: InventoryChecker.isAvailable(items) and NotificationService.getInstance() inside the processor.
GOOD: Inject an inventory interface and a notification port, and pass instances.
WHY: Direct access to the clock or randomness makes results non-deterministic, so tests cannot assert exact values.
BAD: order.setProcessedAt(new Date()) inside the service.
GOOD: Inject a Clock and call clock.now(); tests supply a fixed time.
WHY: Without automated checks, shortcuts such as framework imports in the domain accumulate until refactoring becomes impossible.
BAD: Rely on code review to notice import prisma in the domain layer.
GOOD: Run architecture tests (for example dependency-cruiser or ArchUnit) in CI so violations fail the build.
WHY: Test-only hooks and exposed internals hide the real coupling and leave the design worse than before.
BAD: Make a private method public, or add a test flag, so a test can reach it. GOOD: Fix the coupling by extracting a collaborator and injecting it.
# Find hard-to-test code (concrete instantiation)
rg -n "new [A-Z].*Repository\(|new [A-Z].*Service\(|new [A-Z].*Gateway\(" src# Find side effects in business logic
rg -n "fetch\(|axios\.|fs\.|process\.env" src/domain src/application# Run tests with coverage
npm run test:coverage| Topic | Reference | When to Use |
|---|---|---|
| Boundary verification | references/test-boundary-verification.md | Adding architecture tests that enforce dependency rules in CI |
| Testable design | references/test-testable-design.md | Removing hidden dependencies (statics, singletons, clock) |
| Layer isolation | references/test-layer-isolation.md | Deciding how each layer is tested in isolation |
| Tests are architecture | references/test-tests-are-architecture.md | Reviewing test suites that mock internals or sit apart from the design |
For dependency inversion (DIP), see solid-principles. For boundary design, see clean-architecture. For Humble Object pattern, see design-patterns.
pantheon-org/tekhne
c8416e9
Also appears in
last in sync Aug 28, 2026
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.