Freezegun sustituye fuentes comunes de fecha y hora durante una prueba. Permite verificar expiraciones, plazos y programaciones sin depender del reloj real ni añadir sleep() a la suite.
El problema no es solo la velocidad. Una prueba ligada al reloj de la máquina puede fallar al cambiar el día, durante una transición de hora civil o cuando dos aserciones cruzan un segundo. Congelar un instante convierte esa dependencia externa en una entrada reproducible.
Congelar un instante explícito
from datetime import datetime, timezone
from freezegun import freeze_time
def expiro(limite: datetime) -> bool:
return datetime.now(timezone.utc) >= limite
@freeze_time("2026-08-20 18:00:00+00:00")
def test_expiracion() -> None:
limite = datetime(2026, 8, 20, 17, 59, tzinfo=timezone.utc)
assert expiro(limite)
Usa un offset explícito o UTC para que la prueba no cambie con la zona horaria de la máquina. Incluye el instante anterior, el límite exacto y el posterior. Esas fronteras revelan más errores que muchos horarios arbitrarios.
Decorador, contexto y fixture
El decorador resulta cómodo si toda la prueba comparte un instante. Un gestor de contexto limita el cambio al fragmento que lo necesita:
from datetime import date
from freezegun import freeze_time
def etiqueta_del_dia() -> str:
return date.today().isoformat()
def test_etiqueta_del_dia() -> None:
with freeze_time("2026-12-31"):
assert etiqueta_del_dia() == "2026-12-31"
Una fixture de pytest puede centralizar el instante:
import pytest
from freezegun import freeze_time
@pytest.fixture
def reloj_congelado():
with freeze_time("2026-08-20 18:00:00+00:00") as reloj:
yield reloj
Evita convertirla en una fixture autouse para toda la suite. Eso oculta la dependencia y puede interferir con bibliotecas que miden timeouts, cachés o duraciones. Un nombre descriptivo hace visible el efecto global temporal.
Avanzar sin esperar
Usa tick() para probar una expiración progresiva:
from datetime import datetime, timedelta, timezone
from freezegun import freeze_time
def sigue_vigente(creado: datetime, ttl: timedelta) -> bool:
return datetime.now(timezone.utc) < creado + ttl
def test_token_expira_en_cinco_minutos() -> None:
with freeze_time("2026-08-20 18:00:00+00:00") as reloj:
creado = datetime.now(timezone.utc)
ttl = timedelta(minutes=5)
reloj.tick(delta=timedelta(minutes=4, seconds=59))
assert sigue_vigente(creado, ttl)
reloj.tick(delta=timedelta(seconds=1))
assert not sigue_vigente(creado, ttl)
auto_tick_seconds avanza con cada lectura interceptada. Úsalo solo cuando la cantidad y el orden de las lecturas formen parte del escenario. De lo contrario, una llamada adicional durante una refactorización puede cambiar el resultado sin cambiar la regla de negocio. El avance manual suele ser más claro.
Zonas horarias y cambios de hora
Freezegun controla el instante, pero no corrige un modelo temporal ambiguo. datetime.now() sin zona sigue creando un valor ingenuo. Para eventos globales, usa fechas conscientes de zona y normaliza almacenamiento y comparaciones en UTC. Convierte a la zona del usuario en la presentación.
tz_offset simula una diferencia respecto de UTC, pero no reemplaza una zona IANA con reglas históricas. Si el comportamiento depende del horario de verano, congela un instante UTC, conviértelo con ZoneInfo y prueba la conversión. Incluye horas locales repetidas o inexistentes cuando correspondan al dominio.
Límites de imports y procesos
El patrón from datetime import datetime es habitual y está contemplado, pero extensiones nativas, procesos separados y servicios remotos pueden consultar otro reloj. Un worker iniciado en otro proceso no hereda automáticamente el parche. En esos límites, envía el instante en el mensaje, configura el proceso por separado o inyecta una abstracción de reloj.
Usa ignore solo para módulos que realmente deban ver el tiempo real y documenta la razón. Una lista amplia puede ocultar una integración que continúa ligada al reloj de la máquina.
Cuándo inyectar un reloj
La lógica de dominio suele ser más sencilla si el tiempo es una dependencia explícita:
from collections.abc import Callable
from datetime import datetime, timezone
Clock = Callable[[], datetime]
def puede_renovar(expira: datetime, ahora: Clock) -> bool:
restante = expira - ahora()
return restante.total_seconds() <= 3600
def utc_now() -> datetime:
return datetime.now(timezone.utc)
En producción, pasa utc_now; en pruebas, una función fija. Esto evita un parche global. Freezegun conserva su valor para código heredado, frameworks e integraciones donde cambiar todas las firmas no es razonable. Ambas técnicas pueden coexistir.
Reloj monotónico y concurrencia
El reloj de pared responde "¿qué hora es?". Un reloj monotónico mide tiempo transcurrido y no retrocede cuando se corrige la hora del sistema. Timeouts, reintentos y métricas de duración deberían usar time.monotonic() o una abstracción equivalente.
Las versiones actuales de Freezegun afectan APIs monotónicas en escenarios documentados, pero hora civil y duración siguen siendo conceptos diferentes. Comprueba la versión instalada y crea una prueba específica si un framework asíncrono, loop de eventos o cliente HTTP depende de ellas. Evita congelar globalmente mientras pruebas no relacionadas corren en otros hilos.
Matriz práctica de casos
- instante anterior, exacto y posterior al plazo;
- cambio de día, mes y año;
- año bisiesto cuando febrero afecta la regla;
- entradas conscientes e ingenuas, incluida la invalidación esperada;
- conversión entre UTC y la zona mostrada;
- avance de TTL, renovación y periodo de gracia;
- serialización y recuperación del timestamp;
- procesos o servicios externos fuera del congelamiento.
Comprueba la regla observable de la aplicación, no detalles internos de Freezegun. Así la suite resiste mejor las actualizaciones.
Instalación y mantenimiento
Fija un rango de versiones coherente con la política del proyecto y actualiza deliberadamente. Al actualizar, ejecuta primero las pruebas temporales, en especial integraciones asíncronas y paquetes con código nativo. Si los usuarios envían fechas, prueba el parser por separado: congelar el reloj no valida formatos, offsets ni reglas del calendario.
Diseñar escenarios mantenibles
Elige un instante que explique la regla. Una fecha futura aleatoria dificulta entender la intención, mientras que usar siempre el 1 de enero puede ocultar errores de longitud de mes o año bisiesto. Nombra ventanas de negocio como VENTANA_RENOVACION y explica cualquier periodo de gracia en vez de dejar números sin contexto.
Mantén un motivo de comportamiento por prueba. Un único contexto que verifica expiración, formato, programación y persistencia será difícil de diagnosticar. Separa responsabilidades y reutiliza una fixture pequeña cuando convenga. Tampoco pruebes cada minuto del año si clases de equivalencia y límites cubren la misma regla.
Si la aplicación guarda una fecha al importar un módulo, congelar después del import puede ser demasiado tarde. Suele ser una señal de diseño: calcula el valor cuando se necesite o explicita la dependencia. Recargar módulos en pruebas altera estado global y puede crear fallos dependientes del orden.
Limpia siempre el parche mediante decorador o contexto, incluso si falla una aserción. No inicies el congelamiento manualmente sin detenerlo en un bloque finally. Un estado filtrado puede romper la siguiente prueba en un módulo no relacionado.
Elimina los sleep() usados solo para coordinar aserciones. Una espera real puede pertenecer a una prueba integral de un scheduler externo, pero debe estar aislada y no sustituir la cobertura determinista.
Freezegun permite avanzar manualmente, pero el reloj de pared y el monotónico cumplen funciones distintas. Las duraciones internas deberían usar una fuente monotónica. Prueba el comportamiento real y no supongas que todas las APIs temporales serán interceptadas.
Para lógica central, inyectar una función ahora() hace explícita la dependencia y reduce el acoplamiento. Freezegun es especialmente útil en integraciones con código que llama directamente a datetime.now() o date.today().
La guía de zoneinfo y zonas horarias ayuda a evitar fechas ingenuas. El repositorio oficial de Freezegun, consultado el 22 de julio de 2026, documenta decoradores, contextos, tick, offsets y APIs compatibles.