La programación de pruebas unitarias es un pilar fundamental en el desarrollo de software de calidad. Dominar técnicas como la creación de servicios simulados (mocks y stubs) te permitirá verificar el comportamiento de tu código de manera aislada y controlada, garantizando que cada componente funcione correctamente antes de integrarlo con el resto del sistema.
¿Qué son los servicios MOCK y STUB y cuál es su importancia en las pruebas unitarias?
Los servicios MOCK y STUB son herramientas esenciales para realizar pruebas unitarias efectivas en el desarrollo de aplicaciones. Ambos permiten simular comportamientos de componentes externos como bases de datos, lo que nos ayuda a aislar el código que queremos probar y tener un control total sobre el entorno de prueba.
La principal diferencia entre ellos radica en su complejidad y propósito:
STUB: Es una implementación simple que siempre devuelve la misma respuesta predefinida, sin lógica adicional. Son útiles cuando solo necesitamos simular un comportamiento básico.
MOCK: Es una implementación más avanzada que permite simular comportamientos más complejos, como contar cuántas veces se llama a una función o configurar diferentes respuestas según las circunstancias. Ofrecen mayor control y capacidad de verificación.
Estas herramientas son fundamentales porque nos permiten:
Realizar pruebas aisladas sin depender de componentes externos
Controlar exactamente qué datos se utilizan en las pruebas
Evitar la aleatoriedad, que es "el peor enemigo" de las pruebas unitarias
Simular diferentes escenarios, incluyendo casos de error
¿Cómo implementar un servicio MOCK para pruebas unitarias en Swift?
Para implementar un servicio MOCK efectivo en Swift, debemos crear una clase que implemente el mismo protocolo que el servicio real, pero con comportamientos controlados. Veamos cómo hacerlo paso a paso:
// Eliminamos los MOCK Records aleatorios y agregamos variables controlablesvar fetchRecordsResult:[ExpenseRecord]=[]var saveNewRecordResult:Bool=falsevar updateRecordResult:Bool=falsevar deleteRecordResult:Bool=falsevar totalResult:(Double,Double)=(0,0)
Luego, implementamos los métodos del protocolo para que devuelvan estas variables controlables:
La ventaja clave de este enfoque es que podemos modificar estas variables cada vez que creamos una instancia del MOCK, permitiéndonos controlar exactamente qué datos se utilizan en cada prueba.
¿Cómo crear un servicio STUB simple para pruebas básicas?
Los servicios STUB son aún más simples que los MOCK, ya que siempre devuelven los mismos valores predefinidos sin ninguna lógica adicional. Son perfectos para pruebas donde solo necesitamos que exista una implementación del protocolo, pero no nos importa realmente su comportamiento.
Para crear un STUB en Swift:
Creamos una nueva clase que implemente el protocolo requerido:
Este STUB simplemente devuelve valores predeterminados: una lista vacía, falso para todas las operaciones y ceros para los totales. Es útil cuando necesitamos inyectar una dependencia que cumpla con el protocolo, pero que no vamos a utilizar directamente en la prueba específica.
¿Cómo configurar correctamente las pruebas unitarias para un ViewModel?
Una vez que tenemos nuestros servicios MOCK y STUB listos, necesitamos configurar nuestras pruebas unitarias adecuadamente. Esto implica crear una clase de prueba y configurar el entorno antes y después de cada prueba.
Configuración inicial de la clase de prueba
Para crear una clase de prueba efectiva:
Creamos un nuevo archivo de prueba unitaria:
importXCTest@testableimportGastifyclassHomeViewModelTest:XCTestCase{// Propiedades que necesitaremos en nuestras pruebasvar viewModel:HomeViewModel!var mockDatabaseService:MockDatabaseService!var stubDatabaseService:StubDatabaseService!// Configuración antes de cada pruebaoverridefuncsetUp(){super.setUp() mockDatabaseService =MockDatabaseService() stubDatabaseService =StubDatabaseService()// No inicializamos el viewModel aquí porque dependerá de cada prueba}// Limpieza después de cada pruebaoverridefunctearDown(){ viewModel =nil mockDatabaseService =nil stubDatabaseService =nilsuper.tearDown()}// Aquí irán nuestros métodos de prueba}
Es importante destacar que no inicializamos el ViewModel en el método setUp() porque en diferentes pruebas podríamos querer inyectarle diferentes servicios (el MOCK o el STUB), dependiendo de lo que estemos probando.
Beneficios de una buena configuración de pruebas
Esta estructura de pruebas nos proporciona varios beneficios:
Aislamiento: Cada prueba comienza con un estado limpio y controlado
Flexibilidad: Podemos configurar diferentes escenarios para cada prueba
Mantenibilidad: La estructura clara facilita añadir nuevas pruebas en el futuro
Confiabilidad: Evitamos efectos secundarios entre pruebas al limpiar el estado en tearDown()
La configuración adecuada de las pruebas es tan importante como las pruebas mismas, ya que garantiza que estamos probando exactamente lo que queremos probar, sin interferencias externas.
Los servicios MOCK y STUB son herramientas poderosas para crear pruebas unitarias efectivas. Al implementarlos correctamente, podemos probar nuestro código de manera aislada y controlada, lo que nos permite detectar problemas temprano en el ciclo de desarrollo. ¿Has utilizado estas técnicas en tus proyectos? Comparte tu experiencia en los comentarios y cuéntanos qué estrategias de prueba te han resultado más efectivas.