¿Alguna vez pasaste toda la noche atascado porque Singleton impedía escribir pruebas?
La lógica está bien, pero al añadir pruebas se accede a la DB real y se llama a la API real.
Empecemos por la conclusión.
Singleton obtiene el objeto directamente desde el código, así que no puedes sustituirlo por un objeto falso durante las pruebas.
La inyección de dependencias, en cambio, recibe los objetos necesarios desde fuera y permite cambiarlos libremente en las pruebas.
Hoy veremos la diferencia entre ambos con código real.
¿Por qué Singleton bloquea las pruebas?
Singleton es un objeto que existe una sola vez en todo el programa.
Es práctico: puedes obtenerlo desde cualquier lugar con una sola línea, Database.shared.
Ahí empieza el problema.
Si obtienes el objeto directamente dentro de una función, esta queda fuertemente acoplada a la base de datos real.
Durante las pruebas no hay forma de decirle: «Usa una DB falsa, no la real».
// Obtener Singleton directamente dentro de la función
func saveOrder(_ order: Order) {
// Queda fuertemente acoplado a la DBreal
let db = Database.shared
db.insert(order) // No hay forma de impedirlo en las pruebas
}
Para probar este código, la DB real debe estar activa.
Si la DB está apagada, la prueba también falla.
La lógica no está mal, pero la infraestructura hace que la prueba falle.
¿Cómo se hace la inyección de dependencias?
La inyección de dependencias no es tan complicada como parece.
En lugar de obtener el objeto dentro de la función, créalo fuera y pásalo como argumento.
Literalmente, consiste en «inyectar» lo que necesitas.
// DBRecibirlo desde fuera como parámetro
func saveOrder(_ order: Order, db: Database) {
db.insert(order) // El llamador decide qué tipo de DBes
}
La diferencia es de una sola línea, ¿verdad?
Solo cambiamos lo que se obtenía directamente con Database.shared para recibirlo como parámetro.
Sin embargo, este pequeño cambio transforma por completo las pruebas.
En el código real pasas la DB real; en las pruebas, una DB falsa.
También es habitual recibirlo mediante el inicializador.
class OrderService {
private let db: Database
// Recibir y guardar el objeto desde fuera al crearlo
init(db: Database) { self.db = db }
}
Una vez recibido, puedes usarlo en cualquier parte de la clase como self.db, de forma mucho más limpia.
Sustituir objetos falsos en las pruebas
Ahora viene la parte realmente interesante.
Como la dependencia llega desde fuera, en las pruebas puedes pasar una DB falsa en lugar de la real (normalmente llamada mock).
@Test func Al guardar_el pedido, se_DBentrega a_() {
let fake = FakeDatabase() // Falso, no real
saveOrder(Order(), db: fake)
#expect(fake.savedCount == 1) // Comprobar solo que se llamó a guardar
}
No necesitas iniciar la DB real.
Tampoco necesitas red ni servidores externos. Todo termina al instante en memoria.
Gracias a eso, las pruebas son más rápidas y, sobre todo, estables.
Puedes validar solo la lógica, sin depender de las condiciones externas.
Singleton frente a inyección de dependencias: comparación rápida
He resumido la diferencia en esta tabla.
| Categoría | Uso directo de Singleton | Inyección de dependencias |
|---|---|---|
| Dónde se obtiene el objeto | Se obtiene directamente dentro de la función | Se proporciona desde fuera |
| Sustitución del objeto falso | Prácticamente imposible | Posible libremente |
| Velocidad de las pruebas | Lenta (requiere recursos reales) | Rápida (se procesa en memoria) |
| Estabilidad de las pruebas | Afectada por condiciones externas | Solo se valida la lógica |
| Acoplamiento | Alto | Bajo |
Singleton no es necesariamente malo.
Es práctico cuando realmente solo necesitas una instancia, como ocurre con los valores de configuración.
El problema es el hábito de «obtenerlo directamente desde cualquier lugar». Eso deja las pruebas atadas de manos.
Preguntas frecuentes (Q&A)
P. ¿Necesito obligatoriamente una biblioteca de DI como Swinject para usar inyección de dependencias?
No. Pasar dependencias mediante parámetros o el inicializador, como en el código de hoy, ya es inyección de dependencias. La biblioteca solo lo gestiona automáticamente.
P. ¿No se vuelve desordenado cuando hay demasiados parámetros?
Sí. Por eso normalmente se reciben juntos mediante inyección por inicializador o se agrupan los elementos relacionados. Si los parámetros siguen aumentando, también puede indicar que la clase hace demasiado.
Si las pruebas siguen quedándose bloqueadas por recursos reales, cambia solo un punto siguiendo la idea de hoy.
Cambia el código que obtiene objetos directamente para recibirlos desde fuera.
Ese pequeño hábito hará que disfrutes escribiendo pruebas. ¡Que tengas una buena sesión de código!

