Freezegun substitui fontes comuns de data e hora durante um teste. Isso permite verificar expiração, prazos e agendamentos sem depender do relógio real nem inserir sleep() na suíte.

O problema não é apenas velocidade. Um teste ligado ao relógio da máquina pode falhar na virada do dia, durante uma mudança de horário civil ou quando duas asserções atravessam um segundo. Congelar um instante transforma essa dependência externa em uma entrada reproduzível. Assim, uma falha pode ser repetida localmente e no CI com o mesmo resultado.

Congelar um instante explícito

from datetime import datetime, timezone
from freezegun import freeze_time


def expirou(limite: datetime) -> bool:
    return datetime.now(timezone.utc) >= limite


@freeze_time("2026-08-20 18:00:00+00:00")
def test_expiracao() -> None:
    limite = datetime(2026, 8, 20, 17, 59, tzinfo=timezone.utc)
    assert expirou(limite)

Use um offset explícito ou UTC para evitar um teste que muda conforme o timezone da máquina. Inclua casos imediatamente antes, exatamente no limite e imediatamente depois. Essa fronteira costuma revelar mais erros do que dezenas de horários arbitrários.

Decorator, contexto e fixture

O decorator é conveniente quando todo o teste usa o mesmo instante. Um gerenciador de contexto limita a alteração ao trecho que realmente depende do tempo:

from datetime import date
from freezegun import freeze_time


def rotulo_do_dia() -> str:
    return date.today().isoformat()


def test_rotulo_do_dia() -> None:
    with freeze_time("2026-12-31"):
        assert rotulo_do_dia() == "2026-12-31"

    # Fora do bloco, date.today() volta ao comportamento normal.

Em uma suíte com pytest, uma fixture pode centralizar o instante de referência. Evite, porém, uma fixture autouse que congele o tempo em todos os testes. Ela esconde a dependência e pode interferir em bibliotecas que medem timeout, cache ou duração.

import pytest
from freezegun import freeze_time


@pytest.fixture
def relogio_congelado():
    with freeze_time("2026-08-20 18:00:00+00:00") as relogio:
        yield relogio

O objeto entregue pelo contexto permite avançar para outro instante sem encerrar o teste. Dê à fixture um nome que revele o efeito global temporário.

Avançar o relógio sem esperar

Para expiração progressiva, use tick() no objeto congelado. Isso mantém o teste rápido e deixa claro quanto tempo deve passar:

from datetime import datetime, timedelta, timezone
from freezegun import freeze_time


def ainda_valido(criado_em: datetime, ttl: timedelta) -> bool:
    return datetime.now(timezone.utc) < criado_em + ttl


def test_token_expira_depois_de_cinco_minutos() -> None:
    with freeze_time("2026-08-20 18:00:00+00:00") as relogio:
        criado_em = datetime.now(timezone.utc)
        ttl = timedelta(minutes=5)

        relogio.tick(delta=timedelta(minutes=4, seconds=59))
        assert ainda_valido(criado_em, ttl)

        relogio.tick(delta=timedelta(seconds=1))
        assert not ainda_valido(criado_em, ttl)

Também existe auto_tick_seconds, que avança automaticamente a cada consulta interceptada. Use-o apenas quando a quantidade e a ordem das leituras fazem parte do cenário. Caso contrário, uma chamada adicional a datetime.now() durante uma refatoração muda o resultado sem que a regra de negócio tenha mudado. O avanço manual costuma produzir testes mais legíveis.

Timezones, datas ingênuas e horário de verão

Freezegun controla o instante, mas não corrige um modelo temporal ambíguo. datetime.now() sem timezone continua produzindo uma data ingênua. Para eventos que representam um instante global, prefira valores conscientes de timezone e normalize armazenamento e comparação em UTC. Converta para o fuso do usuário apenas nas bordas de apresentação.

tz_offset pode simular a diferença entre UTC e hora local, mas não substitui uma zona IANA com regras históricas. Se o comportamento depende de uma transição de horário de verão, combine um instante UTC congelado com ZoneInfo e teste explicitamente a conversão. Inclua horários repetidos ou inexistentes quando forem relevantes ao domínio.

Imports que merecem atenção

Freezegun altera referências conhecidas nos módulos carregados, mas a forma de importação ainda afeta a clareza do teste. Este código:

from datetime import datetime


def agora():
    return datetime.now()

é um caso comum e suportado. Mesmo assim, dependências nativas, extensões C, processos separados e serviços remotos podem consultar outra fonte de tempo. Um worker iniciado em outro processo não herda automaticamente o patch do processo de teste. Nesses limites, passe o instante na mensagem, configure o processo separadamente ou injete uma abstração de relógio.

Use ignore apenas para módulos que realmente não devem ser congelados e documente o motivo. Uma lista ampla de exceções pode mascarar uma integração que continua dependente do tempo real.

Quando injetar um relógio

Uma função de domínio fica mais simples de testar quando recebe o instante:

from collections.abc import Callable
from datetime import datetime, timezone

Clock = Callable[[], datetime]


def pode_renovar(expira_em: datetime, agora: Clock) -> bool:
    restante = expira_em - agora()
    return restante.total_seconds() <= 3600


def utc_now() -> datetime:
    return datetime.now(timezone.utc)

Na aplicação, passe utc_now; no teste, passe uma função que retorna um valor fixo. Essa abordagem explicita a dependência e evita patch global. Freezegun continua valioso para código legado, frameworks e testes de integração nos quais alterar todas as assinaturas seria inadequado. As duas técnicas podem coexistir: relógio injetado no núcleo e congelamento nas bordas.

Relógio monotônico e concorrência

Relógio de parede responde à pergunta "que horas são?". Um relógio monotônico mede tempo decorrido e não retrocede quando o sistema ajusta sua hora. Timeouts, tentativas e métricas de duração devem normalmente usar time.monotonic() ou uma abstração equivalente.

Versões atuais do Freezegun também afetam APIs monotônicas em cenários documentados, mas isso não transforma hora civil e duração no mesmo conceito. Verifique a versão instalada e escreva um teste direcionado se uma biblioteca assíncrona, um loop de eventos ou um cliente HTTP depende dessas APIs. Evite congelar globalmente enquanto outros testes executam em threads no mesmo processo, pois o patch pode ser observado fora do caso pretendido.

Casos que uma boa suíte deve cobrir

  • instante anterior, igual e posterior ao prazo;
  • virada de dia, mês e ano;
  • ano bissexto quando fevereiro faz parte da regra;
  • entrada consciente e ingênua de timezone, inclusive a rejeição esperada;
  • conversão entre UTC e o fuso exibido ao usuário;
  • avanço de TTL, renovação e janela de tolerância;
  • serialização e recuperação do timestamp;
  • comportamento do processo ou serviço externo que não participa do congelamento.

Não confirme detalhes internos do Freezegun, como a classe exata retornada pelo patch. Confirme a regra observável da aplicação. Isso mantém a suíte resistente a atualizações da biblioteca.

Instalação e manutenção

Fixe a faixa de versão conforme a política do projeto e atualize deliberadamente. Em uma atualização, execute primeiro os testes que usam tempo, especialmente integrações assíncronas e bibliotecas com código nativo. Se o projeto aceita datas fornecidas por usuários, teste o parser separadamente: congelar o relógio não valida formatos, offsets nem regras de calendário.

Por fim, mantenha o instante escolhido próximo da regra demonstrada. Uma data aleatória muito distante dificulta entender o cenário; usar sempre o primeiro dia do ano também pode esconder calendários reais. Nomeie constantes relevantes, explique janelas de tolerância e remova qualquer sleep() que tenha sido deixado apenas para sincronizar asserções. Testes determinísticos devem falhar por uma mudança de comportamento, não por carga momentânea da máquina.

Freezegun oferece tick e movimento manual do tempo, mas o relógio de parede e o relógio monotônico têm finalidades diferentes. Durações internas normalmente devem usar uma fonte monotônica. Teste o comportamento real da biblioteca usada e não suponha que toda API de tempo será interceptada.

Para lógica central, injetar uma função agora() pode tornar a dependência mais explícita e reduzir o acoplamento do teste à implementação. Use Freezegun principalmente em integrações com código que chama diretamente datetime.now() ou date.today().

O artigo sobre zoneinfo e fusos horários ajuda a evitar datas ingênuas. O repositório oficial do Freezegun, consultado em 22 de julho de 2026, documenta decorators, contextos, tick, timezone offsets e APIs conhecidas.