Pruebas unitarias del SDDatabaseService en Swift Data

Resumen

Aislar el servicio de base de datos y probarlo por separado te permite garantizar que cada acceso y función funcione correctamente, sin depender del ViewModel ni de otras capas. Aquí verás cómo configurar pruebas unitarias en Swift Data paso a paso, desde el setup inicial hasta validar que un registro realmente se guardó en la base.

¿Por qué aislar el servicio de base de datos en pruebas unitarias?

Cuando integras tu ViewModel con la base de datos, ya validaste que ambos hablan bien entre sí. Pero eso no te asegura que el servicio de base de datos, por sí solo, esté haciendo su trabajo correctamente en cada función.

Aislarlo te da control. Si una función falla, sabes exactamente dónde está el problema. Y si mañana cambias el ViewModel, tus pruebas del servicio siguen siendo válidas.

¿Qué es una prueba unitaria de servicio? Es una prueba que valida una sola unidad de código (en este caso, el servicio de base de datos) sin depender de otras capas como la vista o el ViewModel.

¿Cómo configurar el archivo de test para SDDatabaseService?

Dentro del target de test, crea un nuevo archivo desde template, elige Unit Test y nómbralo SDDatabaseServiceTest. Guárdalo en la misma carpeta que el resto de tus pruebas para mantener el orden.

Después, borra los casos de ejemplo que vienen por defecto y agrega el @testable import Gastify (con t minúscula). Así tu archivo de pruebas tiene acceso interno al servicio que vas a evaluar [00:33].

¿Qué hacer en el setup y teardown?

Los métodos setUp y tearDown preparan y limpian el entorno antes y después de cada prueba. Aquí necesitas:

  • Llamar a super.setUp() y super.tearDown() para mantener el comportamiento base.
  • Declarar una función privada auxiliar clearDatabase que limpie los registros.
  • Inicializar la variable databaseService como instancia del servicio que vas a probar.

Como la clase corre en el main actor, el setup usa una expectation y un Task que se ejecuta en el main actor. Dentro llamas a clearDatabase, cierras la expectation y haces el wait correspondiente [01:25].

¿Cómo probar la función para guardar un nuevo registro?

La primera función a evaluar es la que agrega un registro nuevo. La idea es simple: guardas un registro, validas que la función devuelva true, y después confirmas que efectivamente quedó en la base.

El flujo queda así:

  1. Crear un registro random a partir de tu modelo Record.
  2. Llamar a await databaseService.saveNewRecord(record) y guardar el resultado en una variable success.
  3. Hacer XCTAssertTrue(success) para validar que la función reportó éxito.
  4. Ejecutar un fetchRecords con el filtro de hoy para traer los registros guardados.
  5. Hacer XCTAssertTrue(fetchRecords.contains { $0.id == record.id }) para confirmar que el registro existe en la base.

Este segundo assert es clave. Sin él, solo estarías confiando en que la función devolvió true, pero no en que realmente persistió el dato.

¿Por qué hacer dos asserts en una misma prueba? Porque el primero valida la respuesta de la función y el segundo valida el efecto real en la base de datos. Una función puede devolver true sin haber guardado nada.

¿Cómo validar la función de fetchRecords?

Para probar la función que trae los registros, primero necesitas garantizar que haya al menos uno guardado. Por eso reutilizas la función saveNewRecord que ya validaste en la prueba anterior.

El proceso es directo:

  • Creas un registro nuevo.
  • Llamas a saveNewRecord para guardarlo.
  • Ejecutas fetchRecords, que devuelve una lista.
  • Haces un XCTAssertGreaterThan(fetchRecords.count, 0) con un mensaje de error descriptivo por si falla.

Usar greaterThan en lugar de un valor exacto te da flexibilidad: la prueba pasa mientras haya al menos un registro, sin importar si quedaron rastros de pruebas anteriores [03:15].

¿Cómo correr y validar los tests en Xcode?

Ve a tu plan de pruebas y confirma que el nuevo servicio SDDatabaseServiceTest esté agregado y que ambos tests estén seleccionados. Después usa Command + U para correr todas las pruebas.

Xcode hará el build y ejecutará los tests. Cuando terminen, abre el reporte para ver el resultado. Si todos pasan en verde, tu implementación está funcionando como esperas.

¿Qué funciones del servicio te quedan por probar?

Con las dos pruebas anteriores ya tienes el patrón. Ahora puedes replicarlo para las funciones que faltan en el servicio:

  • Actualizar un registro existente: guarda un registro, modifícalo, y valida que los cambios persistan en la base.
  • Eliminar un registro existente: guarda un registro, elimínalo, y valida que ya no aparezca en fetchRecords.
  • Obtener los totales de la base de datos: inserta varios registros con valores conocidos y valida que la suma coincida.

Deja el encabezado de cada función vacío en tu archivo de pruebas para que tengas claro qué te falta. Implementa cada test siguiendo el mismo patrón: prepara los datos, ejecuta la función, valida con asserts tanto la respuesta como el efecto real.

¿Cómo te fue implementando los tests de actualizar, eliminar y obtener totales? Cuéntame en los comentarios qué assert usaste para validar cada caso.