Por qué deberías probar las relaciones entre tus modelos (y no solo la lógica de negocio)

Cuando pensamos en escribir pruebas automatizadas, lo primero que suele venir a la mente son las reglas de negocio: que un cálculo dé el resultado correcto, que una validación rechace lo que debe rechazar, que un flujo termine en el estado esperado. Todo eso es importante. Pero hay una capa más silenciosa, que rara vez recibe atención y que, cuando falla, produce los errores más difíciles de diagnosticar: las relaciones entre modelos.

En este artículo vamos a ver por qué vale la pena dedicarles pruebas propias, qué conviene verificar exactamente, y cómo hacerlo sin complicar el código de prueba. Los ejemplos usan Eloquent, el ORM de Laravel, pero las ideas se trasladan a cualquier ORM con relaciones.

El problema: las relaciones fallan en silencio

Una relación mal definida no suele lanzar un error inmediato. El código compila, la aplicación arranca, y todo parece funcionar hasta que, en algún punto, una consulta devuelve una colección vacía donde debería haber datos, o carga el registro equivocado.

El caso más común aparece cuando el nombre de la clave foránea no sigue la convención que el ORM espera. Muchos frameworks asumen un patrón de nombres por defecto (por ejemplo, que la clave hacia una tabla pedidos se llame pedido_id). Si tu base de datos usa otra convención, el ORM buscará una columna que no existe y la relación devolverá vacío, sin quejarse.

Este tipo de fallo es especialmente traicionero porque no se manifiesta donde está el error, sino mucho más adelante, en una vista que no muestra datos o en un reporte con totales incompletos. Una prueba de relación lo atrapa en el acto.

Qué conviene verificar

Probar una relación no es más que confirmar que, dado un registro, puedes navegar hacia los registros conectados y obtener los correctos. En la práctica, hay cuatro cosas que vale la pena cubrir.

Que la relación carga en ambos sentidos. Si un registro padre tiene muchos hijos, verifica que desde el padre llegas a todos sus hijos, y que desde un hijo llegas a su padre. Las relaciones tienen dos extremos y ambos pueden estar mal definidos de forma independiente.

Que la carga anticipada funciona. La carga anticipada (conocida como eager loading) es la técnica que evita el problema de las N+1 consultas: en lugar de una consulta por cada registro relacionado, se hacen dos en total. Vale la pena una prueba que confirme que, al pedir los datos con carga anticipada, efectivamente se ejecutan las consultas mínimas y no una por cada elemento.

Que el borrado se comporta como esperas. Si configuraste que al borrar un padre se borren sus hijos en cascada, pruébalo. Y presta atención a un detalle: el comportamiento del borrado en cascada puede depender del motor de base de datos. Algunos motores no aplican las reglas de integridad referencial a menos que se activen explícitamente, así que una prueba que “pasa” en tu entorno podría no reflejar lo que ocurre en producción si los motores difieren.

Que los datos de captura histórica se conservan. Este es sutil y muy valioso. A veces guardas una copia de un dato en el momento de una transacción (por ejemplo, el nombre y el valor de algo tal como estaban ese día) en lugar de depender siempre del registro vivo. Una prueba puede confirmar que, si el registro original cambia después, la copia guardada conserva su valor original. Es la diferencia entre un histórico confiable y uno que se reescribe solo.

Cómo escribirlas sin sufrir

El obstáculo habitual para probar relaciones es preparar los datos: para probar un hijo necesitas su padre, que a su vez puede necesitar su propio padre, y así sucesivamente. Aquí es donde las factories hacen el trabajo pesado.

Una factory es una receta para crear registros de prueba con valores realistas. Bien configurada, una factory puede encadenar automáticamente la creación de los registros de los que depende. Así, pedir un registro hijo crea toda su cadena de ancestros sin que tengas que armarla a mano:

// Crea el hijo y, de forma automática, todos sus registros padre
$linea = LineaDeDetalle::factory()->create();

// La relación debería devolver el padre correcto
$this->assertNotNull($linea->documento);

Cuando quieres varios hijos colgando del mismo padre, creas el padre una vez y se lo pasas a los hijos:

$documento = Documento::factory()->create();

Impuesto::factory()->count(3)->create([
    'documento_id' => $documento->id,
]);

// El padre debería ver a sus tres hijos
$this->assertCount(3, $documento->impuestos);

Un consejo que ahorra dolores de cabeza: si tu base de datos no usa la convención de nombres por defecto del ORM, declara la clave foránea de forma explícita en cada relación. No dependas de que el framework la adivine. Es una línea más por relación y elimina de raíz toda una categoría de errores silenciosos.

Una red de seguridad para refactorizaciones

El mayor valor de estas pruebas aparece cuando tienes que modificar la estructura de datos. Agregar tablas, normalizar información que antes estaba mezclada, cambiar cómo se conectan las entidades: son cambios que tocan muchas piezas a la vez y donde es fácil romper una relación sin darte cuenta.

Con un conjunto de pruebas de relaciones en su lugar, cada cambio se valida solo. Si una modificación rompe la conexión entre dos modelos, lo sabes en segundos, no cuando un usuario reporta que un reporte sale vacío. Esa retroalimentación inmediata es lo que convierte una refactorización arriesgada en una rutina controlada.

En resumen

Las relaciones entre modelos son la fontanería de una aplicación: invisibles cuando funcionan, catastróficas cuando fallan. Dedicarles pruebas propias no lleva mucho tiempo, sobre todo apoyándote en factories que preparen los datos, y a cambio obtienes una red de seguridad que atrapa una clase de errores que de otro modo llegarían hasta el usuario final.

La próxima vez que definas una relación, considera escribir junto a ella una prueba breve que confirme que carga bien, en ambos sentidos. Tu yo del futuro, en medio de una refactorización, te lo agradecerá.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *