Как тестировать код с использованием LocalDate.now() в Java?
Что проверяют
Понимаете ли вы, почему скрытая зависимость от системных часов делает тест нестабильным, и знаете ли стандартный способ её убрать. Следом обычно спрашивают, как это устроить в Spring и почему mockStatic не лучший выбор.
Как отвечать
В JDK уже есть нужная абстракция: java.time.Clock. У методов now() в java.time есть перегрузка с часами, а LocalDate.now() внутри просто вызывает LocalDate.now(Clock.systemDefaultZone()). Поэтому своя обёртка вроде DateProvider не нужна.
- в коде: класс получает
Clockчерез конструктор и вызываетLocalDate.now(clock); - в продакшене:
Clock.systemDefaultZone()или, лучше, часы с явной зоной:Clock.system(ZoneId.of("Europe/Moscow")); - в тестах:
Clock.fixed(Instant.parse("2026-02-28T21:30:00Z"), ZoneId.of("Europe/Moscow"))для точного момента,Clock.offset(base, Duration.ofDays(1)), чтобы сдвинуть время; - в Spring: объявить бин
Clock, а в тестах переопределить его через@TestConfigurationс фиксированными часами. МокатьClockчерез Mockito незачем,Clock.fixedпроще и честнее.
Фиксированные часы позволяют прицельно проверить неприятные даты: 29 февраля, конец месяца, смену года, момент около полуночи. В примере выше в UTC ещё 28 февраля, а в Москве уже 1 марта.
Если код трогать нельзя, есть Mockito.mockStatic(LocalDate.class) в блоке try-with-resources. С Mockito 5 отдельная зависимость не нужна, inline mock maker включён по умолчанию. Но это обходной путь: подмена действует только в текущем потоке и прячет проблему дизайна.
Где спотыкаются
Сравнивают результат с LocalDate.now() прямо в тесте. Тест проходит днём и падает, если запуск пришёлся на полночь.
Забывают про часовой пояс. LocalDate.now() берёт зону по умолчанию, CI обычно работает в UTC, и тест, зелёный локально, краснеет на сервере.
Предлагают PowerMock или JMockit. Оба давно не развиваются и плохо дружат с современными JDK, а при наличии Clock они просто не нужны.