{"id":314,"date":"2026-08-03T17:08:04","date_gmt":"2026-08-04T00:08:04","guid":{"rendered":"https:\/\/www.mariatech.com.mx\/blog\/?p=314"},"modified":"2026-08-03T17:08:05","modified_gmt":"2026-08-04T00:08:05","slug":"por-que-deberias-probar-las-relaciones-entre-tus-modelos-y-no-solo-la-logica-de-negocio","status":"publish","type":"post","link":"https:\/\/www.mariatech.com.mx\/blog\/laravel-php\/por-que-deberias-probar-las-relaciones-entre-tus-modelos-y-no-solo-la-logica-de-negocio\/","title":{"rendered":"Por qu\u00e9 deber\u00edas probar las relaciones entre tus modelos (y no solo la l\u00f3gica de negocio)"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Cuando pensamos en escribir pruebas automatizadas, lo primero que suele venir a la mente son las reglas de negocio: que un c\u00e1lculo d\u00e9 el resultado correcto, que una validaci\u00f3n rechace lo que debe rechazar, que un flujo termine en el estado esperado. Todo eso es importante. Pero hay una capa m\u00e1s silenciosa, que rara vez recibe atenci\u00f3n y que, cuando falla, produce los errores m\u00e1s dif\u00edciles de diagnosticar: las relaciones entre modelos.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En este art\u00edculo vamos a ver por qu\u00e9 vale la pena dedicarles pruebas propias, qu\u00e9 conviene verificar exactamente, y c\u00f3mo hacerlo sin complicar el c\u00f3digo de prueba. Los ejemplos usan Eloquent, el ORM de Laravel, pero las ideas se trasladan a cualquier ORM con relaciones.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">El problema: las relaciones fallan en silencio<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Una relaci\u00f3n mal definida no suele lanzar un error inmediato. El c\u00f3digo compila, la aplicaci\u00f3n arranca, y todo parece funcionar hasta que, en alg\u00fan punto, una consulta devuelve una colecci\u00f3n vac\u00eda donde deber\u00eda haber datos, o carga el registro equivocado.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">El caso m\u00e1s com\u00fan aparece cuando el nombre de la clave for\u00e1nea no sigue la convenci\u00f3n que el ORM espera. Muchos frameworks asumen un patr\u00f3n de nombres por defecto (por ejemplo, que la clave hacia una tabla <code>pedidos<\/code> se llame <code>pedido_id<\/code>). Si tu base de datos usa otra convenci\u00f3n, el ORM buscar\u00e1 una columna que no existe y la relaci\u00f3n devolver\u00e1 vac\u00edo, sin quejarse.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Este tipo de fallo es especialmente traicionero porque no se manifiesta donde est\u00e1 el error, sino mucho m\u00e1s adelante, en una vista que no muestra datos o en un reporte con totales incompletos. Una prueba de relaci\u00f3n lo atrapa en el acto.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Qu\u00e9 conviene verificar<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Probar una relaci\u00f3n no es m\u00e1s que confirmar que, dado un registro, puedes navegar hacia los registros conectados y obtener los correctos. En la pr\u00e1ctica, hay cuatro cosas que vale la pena cubrir.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Que la relaci\u00f3n 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.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Que la carga anticipada funciona. La carga anticipada (conocida como <em>eager loading<\/em>) es la t\u00e9cnica 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\u00ednimas y no una por cada elemento.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Que el borrado se comporta como esperas. Si configuraste que al borrar un padre se borren sus hijos en cascada, pru\u00e9balo. Y presta atenci\u00f3n 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\u00edcitamente, as\u00ed que una prueba que &#8220;pasa&#8221; en tu entorno podr\u00eda no reflejar lo que ocurre en producci\u00f3n si los motores difieren.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Que los datos de captura hist\u00f3rica se conservan. Este es sutil y muy valioso. A veces guardas una copia de un dato en el momento de una transacci\u00f3n (por ejemplo, el nombre y el valor de algo tal como estaban ese d\u00eda) en lugar de depender siempre del registro vivo. Una prueba puede confirmar que, si el registro original cambia despu\u00e9s, la copia guardada conserva su valor original. Es la diferencia entre un hist\u00f3rico confiable y uno que se reescribe solo.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">C\u00f3mo escribirlas sin sufrir<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">El obst\u00e1culo 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\u00ed sucesivamente. Aqu\u00ed es donde las factories hacen el trabajo pesado.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Una factory es una receta para crear registros de prueba con valores realistas. Bien configurada, una factory puede encadenar autom\u00e1ticamente la creaci\u00f3n de los registros de los que depende. As\u00ed, pedir un registro hijo crea toda su cadena de ancestros sin que tengas que armarla a mano:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Crea el hijo y, de forma autom\u00e1tica, todos sus registros padre\n$linea = LineaDeDetalle::factory()-&gt;create();\n\n\/\/ La relaci\u00f3n deber\u00eda devolver el padre correcto\n$this-&gt;assertNotNull($linea-&gt;documento);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Cuando quieres varios hijos colgando del mismo padre, creas el padre una vez y se lo pasas a los hijos:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$documento = Documento::factory()-&gt;create();\n\nImpuesto::factory()-&gt;count(3)-&gt;create(&#091;\n    'documento_id' =&gt; $documento-&gt;id,\n]);\n\n\/\/ El padre deber\u00eda ver a sus tres hijos\n$this-&gt;assertCount(3, $documento-&gt;impuestos);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Un consejo que ahorra dolores de cabeza: si tu base de datos no usa la convenci\u00f3n de nombres por defecto del ORM, declara la clave for\u00e1nea de forma expl\u00edcita en cada relaci\u00f3n. No dependas de que el framework la adivine. Es una l\u00ednea m\u00e1s por relaci\u00f3n y elimina de ra\u00edz toda una categor\u00eda de errores silenciosos.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Una red de seguridad para refactorizaciones<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">El mayor valor de estas pruebas aparece cuando tienes que modificar la estructura de datos. Agregar tablas, normalizar informaci\u00f3n que antes estaba mezclada, cambiar c\u00f3mo se conectan las entidades: son cambios que tocan muchas piezas a la vez y donde es f\u00e1cil romper una relaci\u00f3n sin darte cuenta.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Con un conjunto de pruebas de relaciones en su lugar, cada cambio se valida solo. Si una modificaci\u00f3n rompe la conexi\u00f3n entre dos modelos, lo sabes en segundos, no cuando un usuario reporta que un reporte sale vac\u00edo. Esa retroalimentaci\u00f3n inmediata es lo que convierte una refactorizaci\u00f3n arriesgada en una rutina controlada.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">En resumen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Las relaciones entre modelos son la fontaner\u00eda de una aplicaci\u00f3n: invisibles cuando funcionan, catastr\u00f3ficas cuando fallan. Dedicarles pruebas propias no lleva mucho tiempo, sobre todo apoy\u00e1ndote 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\u00edan hasta el usuario final.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La pr\u00f3xima vez que definas una relaci\u00f3n, 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\u00f3n, te lo agradecer\u00e1.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Cuando pensamos en escribir pruebas automatizadas, lo primero que suele venir a la mente son las reglas de negocio: que un c\u00e1lculo d\u00e9 el resultado correcto, que una validaci\u00f3n rechace lo que debe rechazar, que un flujo termine en el estado esperado. Todo eso es importante. Pero hay una capa m\u00e1s silenciosa, que rara vez [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":315,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[6,15],"tags":[],"class_list":["post-314","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-laravel-php","category-mysql"],"_links":{"self":[{"href":"https:\/\/www.mariatech.com.mx\/blog\/wp-json\/wp\/v2\/posts\/314","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.mariatech.com.mx\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.mariatech.com.mx\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.mariatech.com.mx\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.mariatech.com.mx\/blog\/wp-json\/wp\/v2\/comments?post=314"}],"version-history":[{"count":0,"href":"https:\/\/www.mariatech.com.mx\/blog\/wp-json\/wp\/v2\/posts\/314\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.mariatech.com.mx\/blog\/wp-json\/wp\/v2\/media\/315"}],"wp:attachment":[{"href":"https:\/\/www.mariatech.com.mx\/blog\/wp-json\/wp\/v2\/media?parent=314"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.mariatech.com.mx\/blog\/wp-json\/wp\/v2\/categories?post=314"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.mariatech.com.mx\/blog\/wp-json\/wp\/v2\/tags?post=314"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}