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.
##Signos y síntomas
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.
- Matchers demasiado precisos.
toStrictEqualdonde bastaríatoMatchObject, igualdad exacta de números en coma flotante en lugar detoBeCloseTo, o el texto completo de un mensaje cuando una frase clave (toThrow(/expired/)) ya expresa el requisito.
// 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.
Olores relacionados: verificar la estructura del DOM o el estado privado en lugar de lo que puede observar un usuario o quien llama es probar detalles de implementación; una prueba enterrada en la configuración de mocks es Excessive Mocking. La prueba sobreespecificada consiste en verificar más de lo que el comportamiento necesita, aunque cada aserción pase por la API pública.
##Causas del problema
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. - Configuraciones cargadas de mocks: cuando cada colaborador es un mock (Excessive Mocking), verificar cada uno de ellos parece la forma natural de terminar la prueba.
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.
##Tratamiento
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 dirigidas a los hechos que la prueba debe proteger. (Acceder al DOM mediante
containero consultar el estado privado es otro olor: consulta Probar detalles de implementación).
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
toHaveBeenCalledBefore no es un matcher nativo de Jest: proviene de la biblioteca jest-extended (Vitest lo incluye de serie desde la versión 3.0). Sea cual sea tu runner, fija el orden de las llamadas solo cuando ese orden sea en sí el requisito.
##Detectado por
- eslint-jest jest/no-large-snapshots — Prohíbe las instantáneas grandes: una instantánea gigante fija cada detalle incidental de la salida
- eslint-vitest vitest/no-large-snapshots — Prohíbe las instantáneas grandes: una instantánea gigante fija cada detalle incidental de la salida