Strategic architecture, tactical design, and testable code principles (SOLID, Clean Architecture, Design Patterns, Testable Design)
97
97%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
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 databaseBAD: new PostgresRepository() in OrderService constructor.
GOOD: Depend on IRepository interface; inject implementation.
BAD: Test that method X calls method Y internally.
GOOD: Test public behavior: given input, expect output.
BAD: Unit test connects to real database.
GOOD: Unit test uses mock repository; integration test uses real database.
BAD: Skip test, ship untested code.
GOOD: Refactor code to be testable (dependency injection, layer isolation).
BAD: OrderService contains SQL queries and HTTP response formatting.
GOOD: OrderService orchestrates pure entities; adapters handle infrastructure.
# 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:coverageFor dependency inversion (DIP), see solid-principles.
For boundary design, see clean-architecture.
For Humble Object pattern, see design-patterns.
clean-architecture
evals
references
design-patterns
solid-principles
testable-design