Testing de JavaScript: pruebas, aislamiento y contratos | Nicolás Garzón
JavaScript
Copiar function calculateTotal ( items ) {
return items. reduce (
( total, item ) => total + item. price,
0 ,
) ;
} JavaScript
Copiar expect (
calculateTotal ( [
{ price : 10 } ,
{ price : 20 } ,
] ) ,
) . toBe ( 30 ) ; Texto
Copiar Arrange → preparar
Act → ejecutar
Assert → comprobarJavaScript
Copiar const cart = createCart ( ) ;
cart. add ( product) ;
expect ( cart. total) . toBe ( product. price) ; JavaScript
Copiar expect ( service. internalCache. size) . toBe ( 1 ) ; JavaScript
Copiar expect ( await service. find ( product. id) )
. toEqual ( product) ; Prueba la API pública salvo que una unidad interna tenga responsabilidad propia.
Verifican una unidad pequeña con dependencias controladas.
Cálculos.
Validaciones.
Transformaciones.
Máquinas de estado.
Errores del dominio.
Comprueban colaboración real entre varias piezas:
Repository y base de datos de prueba.
Cliente HTTP y servidor simulado.
Módulos y configuración.
DOM y eventos.
Capturan errores de contratos que mocks aislados pueden ocultar.
Ejecutan flujos cercanos a la experiencia real.
Texto
Copiar abrir página
↓
llenar formulario
↓
enviar
↓
ver resultadoSon valiosos para caminos críticos, pero más lentos y sensibles al entorno.
No existe una proporción universal. Busca:
Muchas pruebas rápidas en lógica estable.
Integraciones suficientes para contratos reales.
E2E selectivos para flujos críticos.
Texto
Copiar -1
0
1
máximo
máximo + 1
NaN
Infinity
string
null
undefinedLas fronteras descubren más errores que repetir únicamente el camino feliz.
JavaScript
Copiar it. each ( [
[ 0 , true ] ,
[ 1 , true ] ,
[ - 1 , false ] ,
[ NaN , false ] ,
] ) ( "validates %s" , ( value, expected ) => {
expect ( isValidQuantity ( value) ) . toBe ( expected) ;
} ) ; JavaScript
Copiar expect ( ( ) => createPercentage ( 200 ) )
. toThrow ( RangeError) ; JavaScript
Copiar expect ( ( ) => requireProduct ( [ ] , "p1" ) )
. toThrow ( ProductNotFoundError) ; También verifica propiedades estables, no textos completos si el mensaje puede cambiar.
JavaScript
Copiar await expect ( loadProduct ( "p1" ) )
. resolves. toMatchObject ( { id : "p1" } ) ; JavaScript
Copiar await expect ( loadProduct ( "missing" ) )
. rejects. toBeInstanceOf ( ProductNotFoundError) ; No olvides retornar o await la expectativa; una prueba puede terminar antes del rechazo.
JavaScript
Copiar it ( "fails" , ( ) => {
load ( ) . then ( ( value ) => {
expect ( value) . toBe ( 10 ) ;
} ) ;
} ) ; La prueba exterior puede finalizar antes.
JavaScript
Copiar it ( "works" , async ( ) => {
const value = await load ( ) ;
expect ( value) . toBe ( 10 ) ;
} ) ; JavaScript
Copiar vi. useFakeTimers ( ) ;
const callback = vi. fn ( ) ;
setTimeout ( callback, 1000 ) ;
vi. advanceTimersByTime ( 1000 ) ;
expect ( callback) . toHaveBeenCalledOnce ( ) ; Los fake timers cambian el entorno. Promesas, microtasks y APIs del navegador pueden necesitar pasos adicionales según la herramienta.
No uses timers falsos para esconder un diseño difícil de controlar.
JavaScript
Copiar const repository = {
save : vi. fn ( ) . mockResolvedValue ( product) ,
} ; Un mock verifica interacción, pero puede duplicar una suposición incorrecta sobre la dependencia real.
Prefiere fakes pequeños y tests de contrato cuando la integración importa.
Stub: retorna respuestas preparadas.
Spy: observa llamadas.
Fake: implementación simplificada funcional.
Mock: término usado de forma amplia; a menudo combina expectativas e implementación preparada.
El nombre importa menos que entender qué realidad estás reemplazando.
JavaScript
Copiar const service = createOrderService ( {
repository : fakeRepository,
clock : fakeClock,
idGenerator : fakeIdGenerator,
} ) ; Hace deterministas tiempo, IDs y efectos sin mutar globals.
JavaScript
Copiar const clock = {
now : ( ) => new Date ( "2026-07-23T14:00:00Z" ) ,
} ; No dependas de la hora real en una prueba de reglas de negocio.
JavaScript
Copiar const random = ( ) => 0.5 ; Inyecta o fija una seed para reproducibilidad.
Prueba desde el comportamiento del usuario:
JavaScript
Copiar button. click ( ) ;
expect ( status. textContent) . toBe ( "Saved" ) ; element.click() no reproduce todos los detalles de una interacción real. Herramientas de user-event pueden modelar foco, teclado y secuencias con mayor fidelidad.
Intercepta solicitudes en una frontera cercana a HTTP para probar Request, Response, status y payload, no solo una función mockeada.
Un mock de fetch que siempre retorna un objeto incompleto puede ocultar errores como body consumido o headers.
Son útiles para estructuras grandes relativamente estables, pero pueden convertirse en aprobaciones masivas sin lectura.
Prefiere assertions específicas cuando el comportamiento importante cabe en pocas propiedades.
Genera muchos valores y verifica una propiedad.
Texto
Copiar para cualquier array de números finitos:
sort(result) conserva longitud y elementosAporta en parsers, serialización, algoritmos e invariantes.
Reproduce con una prueba que falla.
Corrige la causa.
Comprueba que la prueba pasa.
Añade casos vecinos.
Coverage muestra líneas, ramas y funciones ejecutadas. No demuestra assertions correctas ni calidad.
Texto
Copiar 100% ejecutado ≠ 100% comportamiento correctoÚsala para encontrar huecos, no como objetivo aislado.
Tiempo real.
Red real.
Estado compartido.
Orden de ejecución.
Datos no limpiados.
Concurrencia.
Selectores frágiles.
No soluciones una prueba flaky añadiendo sleeps arbitrarios. Encuentra la condición no controlada.
JavaScript
Copiar it ( "releases inventory when payment fails" , async ( ) => {
const inventory = {
reserve : vi. fn ( ) . mockResolvedValue ( { id : "r1" } ) ,
release : vi. fn ( ) . mockResolvedValue ( undefined ) ,
} ;
const payments = {
charge : vi. fn ( ) . mockRejectedValue (
new Error ( "Declined" ) ,
) ,
} ;
await expect (
checkout ( order, { inventory, payments } ) ,
) . rejects. toBeInstanceOf ( CheckoutError) ;
expect ( inventory. release)
. toHaveBeenCalledWith ( "r1" ) ;
} ) ; La prueba protege un efecto de compensación, no una línea interna.
Probar implementación interna.
No esperar promesas.
Mockear todo y nunca probar integración.
Usar sleeps para sincronización.
Depender de tiempo o red reales.
Actualizar snapshots sin revisarlos.
Perseguir cobertura sin assertions útiles.
Compartir estado entre pruebas.
Probar solo caminos felices.
Ignorar cleanup de DOM, timers o servidores.
Las pruebas protegen contratos observables.
Unit, integration y E2E responden preguntas distintas.
Los límites merecen casos explícitos.
Promesas deben awaited o retornarse.
Mocks pueden ocultar contratos reales.
Dependencias explícitas facilitan determinismo.
Coverage no equivale a calidad.
Una regresión corregida necesita una prueba reproducible.
Flakiness indica una condición no controlada.
¿Por qué una prueba con 100% de cobertura puede no detectar un cálculo incorrecto?
Respuesta Porque coverage solo muestra que las líneas se ejecutaron. La prueba puede no contener una assertion que verifique el resultado correcto.
Linting, formatting y compatibilidad explica qué problemas pueden detectar las herramientas antes de ejecutar.