Inyección de dependencias
Haz tu código flexible y testeable dependiendo de protocolos, no de implementaciones concretas. Inyección por init, por environment, y por qué importa.
Inyección de dependencias suena grande, pero la idea es simple: en vez de que un objeto cree lo que necesita, se lo pasas desde fuera. Eso te deja cambiar esa pieza —por una falsa en tests, por otra implementación en producción— sin tocar el objeto. Es la base de un código testeable.
- Qué es y por qué inyectar dependencias.
- Depender de protocolos, no de tipos concretos.
- Inyección por inicializador y por environment.
- Sustituir dependencias en tests.
El problema: dependencias fijas
Si un modelo crea directamente su servicio de red, está atado a él: no puedes probarlo sin internet, ni cambiarlo:
@Observable
class Feed {
private let servicio = ServicioReal() // ❌ dependencia rígida
func cargar() async { /* usa servicio */ }
}
La solución: depende de un protocolo
Define un contrato (protocolo) y haz que el modelo dependa de él, recibiéndolo desde fuera:
protocol ServicioFeed {
func publicaciones() async throws -> [Publicacion]
}
@Observable
class Feed {
private let servicio: ServicioFeed
init(servicio: ServicioFeed) { // ✅ inyectado
self.servicio = servicio
}
func cargar() async { /* usa self.servicio */ }
}
Ahora Feed no sabe ni le importa qué servicio es, solo que cumple el contrato. En producción le pasas el real; en tests, uno falso.
Esta es una de las ideas más rentables de toda la ingeniería de software, y en Swift encaja perfecto con los protocolos del Nivel 1. Cuando tu código depende de “algo que sabe traer publicaciones” (un protocolo) en vez de “esta clase concreta que llama a esta URL”, ganas tres cosas de golpe: puedes probarlo sin red, puedes cambiar la implementación sin tocar quien la usa, y el diseño te obliga a pensar en las fronteras entre piezas. Es la diferencia entre un código rígido que se rompe al tocarlo y uno flexible que evoluciona contigo.
Inyección por environment
Para dependencias transversales, combina lo del Nivel 3: inyecta un servicio en el environment y las vistas lo toman de ahí. En iOS moderno, con @Entry defines una clave de environment para tu dependencia y la lees con @Environment. Útil cuando muchas vistas comparten el mismo servicio sin pasarlo por parámetros.
Sustituir en tests
Aquí se cobra la inversión. En un test, le pasas una implementación falsa que devuelve datos controlados:
struct ServicioFalso: ServicioFeed {
func publicaciones() async throws -> [Publicacion] {
[Publicacion(id: 1, title: "Test", body: "…")] // datos fijos
}
}
// en el test:
let feed = Feed(servicio: ServicioFalso())
await feed.cargar()
// ahora compruebas el resultado, sin depender de internet
En muchos ecosistemas, la inyección de dependencias viene con librerías pesadas. En Swift, casi siempre basta con lo que ya sabes: protocolos para los contratos e inicializadores para pasar las dependencias. Empieza así de simple. Los frameworks de DI son para necesidades muy concretas de apps grandes, no un requisito de entrada.
- Extrae un servicio a un protocolo (p. ej.
ServicioFeed). - Haz que un modelo lo reciba por su
initen vez de crearlo. - Escribe una implementación real y una falsa que conformen al protocolo.
- Crea el modelo con la falsa y verifica su comportamiento sin red.