Atlasingeniería

Code review en la prácticaTests que pasan sin probar nadaTema 2

Tests atados al reloj y a la fecha de hoy

Un test que pasa hoy y falla el primer día del mes, o sólo cuando la suite corre después de las nueve de la noche. El tiempo es la dependencia oculta más común, y se resuelve inyectándolo como cualquier otra.

Hay una falla de test que aparece siempre en el peor momento: la que depende del día. El test se escribió un martes, pasó durante semanas, y un día falla sin que nadie haya tocado nada.

Casi siempre es lo mismo: el código pregunta qué hora es, y el test asume una respuesta.

Las cuatro formas de atarse al reloj

El patrónCuándo fallaCómo se descubre
Comparar contra la fecha de hoy calculada en el test igual que en el códigoNunca falla, y tampoco prueba nada: los dos tienen el mismo errorSólo en producción
Datos de prueba con fechas fijas del pasadoEl día que la lógica usa «últimos 30 días» y el dato quedó afueraFalla de golpe, meses después
Lógica que depende del cambio de día, mes o añoEl primer día del mes, o a fin de añoFalla justo cuando nadie está mirando
Zona horaria del entornoCuando corre en un servidor con otra zona que la máquina de desarrolloPasa local y falla en el pipeline
El primero es el peor: un test verde que no verifica nada.

El de la zona horaria es el clásico de fin de día: una fecha guardada en tiempo universal que, al formatearse en una zona con desplazamiento negativo, cae un día antes. El test pasa a la mañana y falla a la noche.

El tiempo como dependencia

La solución no es evitar las fechas sino dejar de pedírselas al sistema desde el medio de la lógica.

Tres niveles, de menos a más

  1. Pasar la fecha como parámetro. Lo más simple y casi siempre suficiente: la función que decide si algo venció recibe el momento de referencia en vez de consultarlo.
  2. Inyectar un reloj. Una dependencia con un método que devuelve el ahora, reemplazable en el test. Sirve cuando el momento se consulta en varios lugares del mismo flujo.
  3. Congelar el tiempo en el test, con las utilidades del framework. Útil para código que no se puede refactorizar, y conviene como último recurso: es global y se olvida de descongelar.

Los casos que hay que probar

Con el tiempo bajo control, aparecen los casos de borde que antes eran imposibles de escribir.

Antes de seguir, predecí

Una función decide si una promoción está vigente. ¿Cuál es el caso más importante de probar?

Y hay cuatro que conviene tener en la lista cuando el dominio involucra fechas: el cambio de día en distintas zonas, el fin de mes con meses de distinta duración, el año bisiesto y los cambios de horario de verano donde apliquen.

Lo mismo vale fuera de los tests

Un test que depende del reloj es el síntoma; la causa es código que consulta la hora en cualquier parte. Eso también complica producción: reproducir un problema reportado “ayer a la noche” es imposible si no se puede simular ese momento.

Dos reglas que salen de esto y aparecen seguido en las reviews: guardar siempre en tiempo universal y convertir sólo al mostrar; y registrar la fecha de referencia en los procesos por lotes, para que un reproceso pueda correr con la fecha de entonces y no con la de hoy.

Más a fondo · nivel seniorLa fecha del negocio no siempre es la del reloj

En muchos dominios existe una fecha contable o de proceso que no coincide con el momento real: un cierre que se corre a las tres de la mañana pertenece al día anterior. Cuando eso pasa, el “ahora” del sistema no es la hora del servidor sino un dato del negocio, y modelarlo como tal evita una familia entera de errores que ninguna inyección de reloj resuelve.

Probá cuál de los tests no sirve

Código bajo prueba

const isExpired = (subscription, now) => {
const gracePeriodInDays = 3;
const deadline = new Date(subscription.endsAt);
deadline.setDate(deadline.getDate() + gracePeriodInDays);
return now > deadline;
};

Metele un bug

El código está como lo escribió quien lo programó: toda la suite pasa.

La suite

  • pasauna suscripción terminada hace una semana está vencidaisExpired({ endsAt: "2026-01-01" }, new Date("2026-01-08")) === true
  • pasadentro del período de gracia todavía no está vencidaisExpired({ endsAt: "2026-01-01" }, new Date("2026-01-03")) === false
  • pasacalcula bien respecto de hoyisExpired({ endsAt: hoyMenos(10) }, new Date()) === true

La tercera prueba es la que repite la cuenta del código con las mismas funciones del código. Nunca se pone en rojo, porque si el código se equivoca, ella se equivoca igual. Es el patrón más difícil de ver a ojo y el más fácil de ver así.

El reloj como dependencia

Cierre

Autoevaluación

¿Lo entendiste?

Un test calcula la fecha esperada con la misma fórmula que el código. ¿Qué problema tiene?
¿Cuál es la forma más simple de sacar el reloj del medio de la lógica?