Pruebas Unitarias para View Models en Swift
Clase 4 de 15 • Curso de Swift Unit Testing
Resumen
La prueba de un ViewModel en Swift es un paso crucial para garantizar la calidad de nuestras aplicaciones iOS. Al implementar pruebas unitarias efectivas, podemos verificar que nuestro código funciona correctamente antes de que llegue a manos de los usuarios. En este artículo, exploraremos cómo probar un ViewModel que realiza solicitudes asíncronas, identificando buenas prácticas y posibles mejoras en nuestra arquitectura.
¿Cómo probar un ViewModel con solicitudes asíncronas?
Antes de sumergirnos en la implementación de pruebas, es importante entender el contexto de nuestro ViewModel. En este caso, estamos trabajando con un AgentsViewModel
que contiene una lista de agentes y una función principal llamada fetchAgents()
. Esta función realiza una solicitud a una URL específica y, una vez completada, actualiza la lista de agentes.
Para probar este ViewModel, necesitamos:
- Importar correctamente nuestro proyecto utilizando
@testable import
. - Inicializar el ViewModel en el método
setUp()
. - Limpiar el ViewModel en el método
tearDown()
. - Crear funciones de prueba específicas para cada comportamiento que queremos verificar.
Configuración inicial del caso de prueba
Lo primero que debemos hacer es configurar nuestro archivo de prueba:
import XCTest
@testable import ValorantTestCase
class AgentsViewModelTests: XCTestCase {
var viewModel: AgentsViewModel!
override func setUp() {
super.setUp()
viewModel = AgentsViewModel()
}
override func tearDownWithError() throws {
viewModel = nil
super.tearDownWithError()
}
}
En este código, estamos:
- Importando el framework XCTest para realizar pruebas.
- Importando nuestro proyecto principal con
@testable import ValorantTestCase
. - Declarando una variable para nuestro ViewModel.
- Inicializando el ViewModel en el método
setUp()
. - Limpiando el ViewModel en el método
tearDownWithError()
.
Implementación de la prueba para fetchAgents
Ahora, vamos a implementar nuestra primera prueba para verificar que la función fetchAgents()
funciona correctamente:
func testFetchAgentsSuccess() {
let expectation = self.expectation(description: "fetch agents")
viewModel.fetchAgents()
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
XCTAssertFalse(self.viewModel.agents.isEmpty)
XCTAssertNotNil(self.viewModel.agents.first?.displayName)
expectation.fulfill()
}
wait(for: [expectation], timeout: 5)
}
En esta prueba:
- Creamos una expectativa para manejar la naturaleza asíncrona de la función
fetchAgents()
. - Llamamos a la función que queremos probar.
- Utilizamos
DispatchQueue.main.asyncAfter
para esperar 3 segundos antes de verificar los resultados. - Verificamos que la lista de agentes no esté vacía con
XCTAssertFalse
. - Verificamos que el primer agente tenga un nombre de visualización con
XCTAssertNotNil
. - Cumplimos la expectativa con
expectation.fulfill()
. - Esperamos hasta 5 segundos para que se cumpla la expectativa.
Esta prueba nos asegura que estamos haciendo correctamente la solicitud a la API y que estamos obteniendo una respuesta válida.
¿Por qué es importante separar las capas en nuestra arquitectura?
Aunque nuestra prueba funciona correctamente, hay un problema fundamental en nuestro diseño: nuestro ViewModel está estrechamente acoplado a la capa de servicio. Esto significa que nuestras pruebas dependen de un servicio externo, lo que puede causar problemas si:
- El servicio de API está caído.
- El servicio devuelve una respuesta inesperada.
- Queremos ejecutar pruebas en un entorno sin conexión a Internet.
Esta dependencia hace que nuestras pruebas sean frágiles y poco confiables. Una buena arquitectura nos permitiría aislar nuestro ViewModel de la capa de servicio, lo que facilitaría la creación de pruebas más robustas.
Mejores prácticas para pruebas unitarias
Para mejorar nuestras pruebas, podríamos:
- Implementar inyección de dependencias: Permitir que el ViewModel reciba un servicio como parámetro, lo que nos permitiría inyectar un servicio simulado durante las pruebas.
- Crear mocks o stubs: Implementar versiones simuladas de nuestros servicios que devuelvan respuestas predefinidas.
- Utilizar protocolos: Definir interfaces para nuestros servicios, lo que nos permitiría crear implementaciones alternativas para pruebas.
Estas técnicas nos ayudarían a crear pruebas más confiables y menos dependientes de servicios externos.
¿Qué tipos de pruebas podemos implementar?
En las próximas clases, exploraremos diferentes tipos de pruebas que podemos implementar en nuestro proyecto:
- Pruebas unitarias: Prueban componentes individuales de nuestro código.
- Pruebas de integración: Verifican que diferentes componentes funcionen correctamente juntos.
- Pruebas de UI: Aseguran que la interfaz de usuario funcione como se espera.
Cada tipo de prueba tiene su propio propósito y técnicas específicas, y juntas forman una estrategia completa de pruebas para nuestra aplicación.
Las pruebas son una parte fundamental del desarrollo de software de calidad. Al implementar pruebas efectivas, podemos detectar problemas temprano, refactorizar con confianza y entregar un producto más confiable a nuestros usuarios. ¿Has implementado pruebas en tus proyectos de Swift? Comparte tu experiencia en los comentarios.