Pruebas de Integración con Base de Datos en SwiftData
Clase 8 de 15 • Curso de Swift Unit Testing
Resumen
La integración de pruebas en el desarrollo de aplicaciones es fundamental para garantizar que nuestros componentes funcionen correctamente cuando interactúan con sistemas reales. En este artículo, exploraremos cómo realizar pruebas de integración para un ViewModel que se conecta a una base de datos SwiftData, asegurando que nuestra lógica de negocio funcione correctamente en un entorno más cercano a la producción.
¿Cómo realizar pruebas de integración con SwiftData?
Después de haber probado nuestro ViewModel con mocks y stubs, el siguiente paso lógico es verificar su funcionamiento cuando interactúa con una base de datos real. Las pruebas de integración nos permiten validar que la comunicación entre nuestros componentes funciona correctamente en un entorno más cercano al de producción.
Para comenzar, necesitamos entender la estructura de nuestra capa de servicios. En este caso, tenemos un servicio de base de datos implementado con SwiftData (SDDatabaseService
) que proporciona funcionalidades como guardar, actualizar, eliminar registros y obtener totales.
Configuración del archivo de prueba
Para crear nuestras pruebas de integración, seguiremos estos pasos:
-
Crear un nuevo archivo de prueba desde la plantilla:
- Seleccionar "New File from Template"
- Elegir "Unit Test"
- Nombrar el archivo (en este caso "HomeViewModelIntegrationTest")
-
Importar el proyecto que queremos probar:
import XCTest
@testable import Gastify
- Implementar los métodos de configuración:
override func setUp() {
super.setUp()
// Inicialización del servicio de base de datos y ViewModel
let expectation = expectation(description: "Initialize database service")
Task { @MainActor in
databaseService = SDDatabaseService()
viewModel = HomeViewModel(databaseService: databaseService)
expectation.fulfill()
}
wait(for: [expectation], timeout: 2.0)
clearDatabase()
}
override func tearDown() {
viewModel = nil
databaseService = nil
super.tearDown()
}
Es importante destacar que estamos utilizando el servicio real de base de datos, no un mock. Esto es lo que hace que sea una prueba de integración y no una prueba unitaria.
Manejo de la asincronía en pruebas
Un aspecto crucial al probar código que interactúa con bases de datos es el manejo de operaciones asíncronas. Observa cómo utilizamos expectation y wait para asegurarnos de que las operaciones asíncronas se completen antes de continuar:
let expectation = expectation(description: "Initialize database service")
// Código asíncrono
expectation.fulfill()
wait(for: [expectation], timeout: 2.0)
Garantizando la integridad de las pruebas
Para asegurar que cada prueba comience con un estado limpio, implementamos una función auxiliar que limpia la base de datos:
private func clearDatabase() {
Task {
await databaseService.clearDatabase()
}
}
Esta función se llama al inicio de cada prueba, garantizando que los resultados de una prueba no afecten a las siguientes.
¿Cómo probar la obtención de registros desde la base de datos?
Una vez configurado nuestro entorno de pruebas, podemos implementar pruebas específicas para las funcionalidades que interactúan con la base de datos:
func testGetRecords() {
let expectation = expectation(description: "Get records")
Task {
await viewModel.getRecords()
expectation.fulfill()
}
wait(for: [expectation], timeout: 2.0)
XCTAssertNotNil(viewModel.records)
}
En este test, verificamos que después de llamar a getRecords()
, la propiedad records
del ViewModel no sea nula. Inicialmente, habíamos incluido una verificación adicional para asegurar que la lista de registros no estuviera vacía:
XCTAssertFalse(viewModel.records.isEmpty)
Sin embargo, esta verificación fallaba porque nuestra función clearDatabase()
elimina todos los registros antes de cada prueba. Es fundamental entender el estado inicial de nuestros datos para escribir aserciones correctas.
Depuración de pruebas fallidas
Cuando una prueba falla, XCTest proporciona información valiosa sobre la causa del fallo. Al hacer clic en el error en el reporte de pruebas, nos lleva directamente a la línea problemática y muestra el motivo del fallo.
En nuestro caso, la prueba fallaba porque esperábamos que la lista de registros no estuviera vacía, pero habíamos limpiado la base de datos justo antes de ejecutar la prueba. Al eliminar esta aserción incorrecta, nuestras pruebas pasaron correctamente.
¿Qué otros aspectos debemos probar?
Además de probar la obtención de registros, es importante verificar otras funcionalidades que interactúan con la base de datos, como:
- Obtención de totales
- Guardado de nuevos registros
- Actualización de registros existentes
- Eliminación de registros
Cada una de estas funcionalidades requiere su propio método de prueba, siguiendo un patrón similar al que hemos visto para testGetRecords()
.
Las pruebas de integración son una parte esencial del proceso de desarrollo que nos ayuda a detectar problemas que podrían no ser evidentes en las pruebas unitarias. Al probar nuestro ViewModel con una base de datos real, podemos estar más seguros de que nuestra aplicación funcionará correctamente en producción.
¿Has implementado pruebas de integración en tus proyectos? ¿Qué desafíos has encontrado al probar componentes que interactúan con bases de datos? Comparte tu experiencia en los comentarios.