Post

Testando código que depende do Tempo

Testando código que depende do Tempo

Testar lógica que depende da hora atual (LocalDateTime.now() ou System.currentTimeMillis()) é um pesadelo. Se o seu teste roda às 23:59 e termina às 00:01, ele pode falhar aleatoriamente. Como tornar o tempo determinístico nos seus testes?

O Inimigo do Determinismo

Um bom teste deve ser determinístico: rodar 1000 vezes e passar 1000 vezes. O tempo do sistema é o oposto disso; ele está sempre mudando. Se você tem uma regra de “desconto para compras feitas após as 18h”, você não pode esperar dar 18h para rodar o teste.

O Jeito Errado (Hardcoded)

1
2
3
4
5
public void process() {
    if (LocalDateTime.now().getHour() > 18) { // Jamais faça isso!
        applyDiscount();
    }
}

Problema: Este código é impossível de testar de forma confiável sem mudar o relógio do sistema operacional (o que é uma ideia terrível).

O Jeito Certo: Abstraia o Relógio (Java 8+ Clock)

O Java 8 introduziu a classe java.time.Clock. Em vez de chamar o tempo estático, você pede o tempo para o relógio.

  1. Injete o Clock no seu Serviço:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    @Service
    public class DiscountService {
    private final Clock clock;
    
    public DiscountService(Clock clock) {
        this.clock = clock;
    }
    
    public void process() {
        if (LocalDateTime.now(clock).getHour() > 18) {
            applyDiscount();
        }
    }
    }
    
  2. No Código de Produção: Injete o Clock.systemDefaultZone().

  3. No Código de Teste: Injete um Fixed Clock.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    @Test
    void shouldApplyDiscountAfter18h() {
    // Congela o tempo às 19:00 de um dia específico
    Instant fixedInstant = Instant.parse("2024-03-10T19:00:00Z");
    Clock fixedClock = Clock.fixed(fixedInstant, ZoneId.of("UTC"));
        
    DiscountService service = new DiscountService(fixedClock);
    service.process();
        
    assertTrue(discountApplied);
    }
    

Vantagens dessa abordagem

  • Determinismo total: O tempo nunca muda durante o teste.
  • Simulação de cenários: Você pode testar anos bissextos, viradas de mês ou horários de verão apenas mudando o fixedInstant.
  • Sem Mocks complexos: Você usa uma classe real do Java (Clock.fixed) em vez de tentar mockar métodos estáticos com bibliotecas pesadas.

Curiosidade: Outras Linguagens

Quase todas as linguagens modernas seguem esse padrão. No mundo Node/JS, usamos bibliotecas como sinon para fazer o “fake timers”. No Go, passamos uma interface de TimeProvider.

Conclusão: Lições de um “Bate-Relógio”

No início da minha carreira, passei uma madrugada inteira tentando entender por que um teste de integração de agendamento financeiro só falhava no servidor de CI às segundas-feiras. O culpado? O fuso horário do servidor e o uso direto de Instant.now(). Aquele incidente me ensinou que o tempo é uma dependência externa tão crítica quanto um banco de dados ou uma API. Hoje, não escrevo uma linha de lógica temporal sem injetar um Clock. O controle sobre o tempo é, literalmente, a diferença entre um código que “parece funcionar” e um código que é matematicamente previsível.

This post is licensed under CC BY 4.0 by the author.