Prueba sobreespecificada.
Una prueba sobreespecificada afirma mucho más de lo que requiere el comportamiento bajo prueba (fija cadenas de salida exactas, formas de objeto completas, el orden de las colecciones o cada llamada a un colaborador interno), de modo que se rompe cada vez que cambia un detalle de implementación no relacionado.
##Signs and Symptoms
Reconoces una prueba sobreespecificada por lo que la rompe: una refactorización inofensiva o un cambio incidental hace fallar las pruebas aunque el comportamiento que describen siga siendo correcto.
Señales habituales:
- Afirma sobre detalles irrelevantes. Espacios en blanco o marcado exactos, cadenas completas de mensajes de error, texto de logs, formato o valores volátiles como marcas de tiempo, UUID e identificadores autoincrementales.
- Igualdad del objeto completo cuando solo importa un campo.
toEqual({...el objeto entero...})cuando la prueba trata en realidad de una sola propiedad. - Aserciones sensibles al orden sobre datos lógicamente sin orden. Fijar el orden de un conjunto, de las claves de un mapa o de los resultados de una consulta que el contrato no garantiza.
- Verificación de comportamiento de colaboradores internos. Mocks que afirman recuentos exactos de llamadas, valores argumento por argumento y el orden de las llamadas de dependencias internas en lugar del resultado final observable.
- Snapshots gigantes que capturan todo un árbol renderizado o un cuerpo de respuesta, de modo que cualquier cambio anidado obliga a volver a registrarlo.
- Hurgar en la implementación. Consultar el estado privado o la estructura del DOM (
container.querySelector, campos internos) en lugar del comportamiento público y observable por el usuario.
// Sobreespecificada: fija el marcado exacto, una marca de tiempo volátil y cada llamada a colaboradores
test('renders user badge', () => {
const html = renderBadge(user);
expect(html).toBe('<span class="badge badge--admin">Ada Lovelace</span>'); // marcado exacto
expect(analytics.track).toHaveBeenCalledTimes(1); // recuento de llamadas internas
expect(analytics.track).toHaveBeenCalledWith('badge_render', { ts: 1718445693221 }); // volátil
});
Una prueba de fuego útil: si puedes cambiar la implementación sin cambiar el comportamiento documentado y la prueba sigue fallando, está sobreespecificada.
##Reasons for the Problem
Por qué ocurre
- Una mentalidad de «más aserciones = más exhaustivo»: los desarrolladores fijan todo lo que ven, confundiendo lo completo con lo correcto.
- El instrumental convierte la sobreespecificación en el camino de menor resistencia:
toMatchSnapshot()registra la salida completa, y los frameworks de mocking hacen trivial afirmar cada interacción (toHaveBeenCalledWith, orden de las llamadas, recuentos). - Copiar y pegar un literal de objeto real en
toEqualen lugar de afirmar solo el campo del que trata la prueba. - Probar a través de ganchos de implementación cómodos (nodos del DOM, campos privados) en lugar del contrato público.
Por qué resulta perjudicial
En xUnit Test Patterns de Gerard Meszaros, esta es la causa raíz del olor a Prueba frágil (Fragile Test), atribuida a Software sobreespecificado (Overspecified Software) y a la Sensibilidad al comportamiento (Behavior Sensitivity): las pruebas quedan acopladas a cómo funciona el código en lugar de a qué produce, de modo que fallan ante cambios que no afectan al comportamiento.
- Mantenibilidad. Cada refactorización desencadena una cascada de fallos de pruebas no relacionados, lo que encarece mantener la suite en verde y desincentiva activamente la refactorización.
- Fiabilidad / confianza. Las pruebas que «gritan que viene el lobo» sobre código correcto entrenan al equipo para ignorar los fallos o volver a registrarlos a ciegas (sobre todo con los snapshots).
- Falsa confianza. La sobreespecificación suele coincidir con una infraafirmación del resultado significativo: una prueba puede fijar una marca de tiempo y una línea de log y, sin embargo, no comprobar nunca el valor que de verdad le importa al usuario. Como lo expresa la literatura sobre pruebas de interacción, la sobreespecificación es «verificar cosas que no forman parte del resultado final», casi siempre afirmando interacciones en lugar de resultados.
- Legibilidad. Un muro de aserciones incidentales oscurece el único comportamiento que la prueba debería documentar.
##Treatment
Afirma solo lo que requiere el comportamiento bajo prueba y prefiere verificar el resultado final antes que las interacciones internas.
- Nombra el único resultado que documenta la prueba y afirma eso, nada más. Divide los resultados realmente distintos en pruebas separadas y bien nombradas en lugar de una megaaserción.
- Usa comparadores parciales/laxos en lugar de la igualdad exacta del objeto completo:
expect.objectContaining,toMatchObject,arrayContaining,stringContaining/stringMatchingyexpect.any(...)para los campos que no puedes o no debes fijar. - Neutraliza los datos volátiles (marcas de tiempo, identificadores, valores aleatorios) con comparadores de propiedades (
expect.any(String)) o inyectando un reloj/generador de identificadores fijo; no codifiques a mano el valor de hoy. - Elimina las suposiciones de orden para los datos sin orden: afirma la pertenencia (
arrayContaining) u ordena antes de comparar. - Prefiere la verificación de estado a la verificación de comportamiento. Usa stubs para las consultas (no hagas aserciones sobre ellas); verifica una llamada a un colaborador solo cuando esa llamada sea el efecto secundario observable (un comando), y evita afirmar recuentos/orden exactos de colaboradores puramente internos.
- Mantén los snapshots pequeños e intencionados, o sustituye un snapshot desbordante por unas pocas aserciones específicas. Consulta mediante la API orientada al usuario (p. ej.
getByRole/getByTextde Testing Library) en lugar de acceder acontainer/nodos del DOM.
Antes → después:
// Antes: acopla la prueba a campos volátiles y a la secuencia de llamadas internas
expect(result).toEqual({
id: '8f3c-92a1-...', // uuid aleatorio
createdAt: '2026-06-15T10:01:33.221Z',// Date.now()
name: 'Ada Lovelace',
role: 'admin',
});
expect(logger.info).toHaveBeenCalledTimes(3);
expect(db.connect).toHaveBeenCalledBefore(db.query);
// Después: afirma solo el comportamiento del que trata esta prueba
expect(result).toMatchObject({ name: 'Ada Lovelace', role: 'admin' });
// id/createdAt son incidentales; el logging y el orden de las llamadas son detalles de implementación: no los fijes
##Detected by
- eslint-jest jest/no-large-snapshots — no-large-snapshots
- eslint-vitest vitest/no-large-snapshots — no-large-snapshots
- eslint-testing-library testing-library/no-node-access — no-node-access
- eslint-testing-library testing-library/no-container — no-container