Inyección de dependencias en Swift: composición y testing
Constructor injection, límites de protocolos, composition root, dobles de pruebas y trade-offs.
Concepto y propósito
DI consiste en entregar colaboradores desde fuera en vez de crearlos dentro. Es una técnica; Dependency Inversion es el principio de depender de abstracciones. Constructor injection deja visibles las dependencias obligatorias y facilita pruebas.
struct Account: Sendable { let id: String }
protocol AccountsRepository: Sendable {
func load() async throws -> [Account]
}
struct LoadAccounts {
let repository: any AccountsRepository
func execute() async throws -> [Account] {
try await repository.load()
}
}Composition root
Construye la red de dependencias en un punto claro de la aplicación: API client, repositorio real, casos de uso y ViewModel. En proyectos pequeños, un ensamblaje explícito suele ser más legible que un contenedor de DI.
struct APIClient: Sendable { }
struct RemoteAccountsRepository: AccountsRepository {
let api: APIClient
func load() async throws -> [Account] { [] }
}
func makeAccountsUseCase() -> LoadAccounts {
LoadAccounts(repository: RemoteAccountsRepository(api: APIClient()))
}Testing con dobles
El test double debería conservar la semántica de éxito y error del contrato real. No ocultes errores convirtiéndolos en [] si el repositorio real los propaga.
struct AccountsStub: AccountsRepository {
let result: Result<[Account], any Error>
func load() async throws -> [Account] { try result.get() }
}
let success = LoadAccounts(repository: AccountsStub(
result: .success([Account(id: "A1")])
))| Estilo | Ventaja | Riesgo |
|---|---|---|
| Inicializador | Dependencias visibles y obligatorias | Más código en el ensamblaje |
| Parámetro | Útil para operaciones concretas | Firmas largas |
| Environment o locator | Acceso cómodo | Dependencias implícitas |
| Framework DI | Grafos grandes | Configuración y fallos en ejecución |
Trade-offs
No crees un protocolo para cada clase sin motivo. Úsalo en límites reales o donde facilite sustituir implementaciones y probar. Un Singleton no equivale a DI: crea acceso global y puede acoplar tests. Cuida el ciclo de vida y el aislamiento de actores.
Notas personales de estudio. Verifica los matices según la versión de Swift/iOS y la documentación oficial.