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ón | Cuándo falla | Cómo se descubre |
|---|---|---|
| Comparar contra la fecha de hoy calculada en el test igual que en el código | Nunca falla, y tampoco prueba nada: los dos tienen el mismo error | Sólo en producción |
| Datos de prueba con fechas fijas del pasado | El día que la lógica usa «últimos 30 días» y el dato quedó afuera | Falla de golpe, meses después |
| Lógica que depende del cambio de día, mes o año | El primer día del mes, o a fin de año | Falla justo cuando nadie está mirando |
| Zona horaria del entorno | Cuando corre en un servidor con otra zona que la máquina de desarrollo | Pasa local y falla en el pipeline |
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
- 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.
- 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.
- 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í
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
El reloj como dependencia
Cierre
Autoevaluación
¿Lo entendiste?
Práctica