Strategic architecture, tactical design, and testable code principles (SOLID, Clean Architecture, Design Patterns, Testable Design)
71
89%
Does it follow best practices?
Impact
—
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.
.tessl-plugin
clean-architecture
references
design-patterns
references
solid-principles
testable-design