Тестирование кода, зависящего от текущего времени, требует
детерминированности: любые вызовы системы времени должны возвращать
фиксированные значения, иначе тесты становятся нестабильными. Библиотека
Luxon, основанная на Date и Intl,
предоставляет несколько уровней управления текущим временем, что
позволяет изолировать временную зависимость как на уровне приложения,
так и на уровне тестового окружения.
Внутренне Luxon получает текущее время через системные часы JavaScript. Основная точка входа:
DateTime.now()
Этот метод использует текущее значение времени системы через
Date.now() (или эквивалентную абстракцию внутри
библиотеки). Таким образом, любое использование
DateTime.now() напрямую зависит от реального времени
выполнения программы.
Дополнительно Luxon использует глобальную настройку:
Settings.now
Эта функция определяет источник «текущего времени» для всех операций,
которые опираются на now.
Наиболее прямой способ мокирования времени в Luxon — переопределение
Settings.now.
import { Settings } from "luxon";
Settings.now = () => 0;
После такого изменения:
DateTime.now().toMillis(); // всегда 0
Это делает все вычисления времени детерминированными, что особенно важно при тестировании:
Settings.now = () => 1672531200000; // фиксированная дата
const dt = DateTime.now();
dt.toISO(); // всегда один и тот же результат
Для корректной работы в тестовой среде важно восстанавливать оригинальное значение:
const realNow = Settings.now;
Settings.now = () => 1000;
// тесты
Settings.now = realNow;
Глобальное изменение Settings.now влияет на всю среду
выполнения. Это удобно для простых тестов, но становится проблемой при
параллельных тестах или при наличии нескольких сценариев с разным
временем.
Более безопасный подход — локализация мока через обёртку:
import { DateTime } from "luxon";
class Clock {
now() {
return DateTime.now();
}
}
В тестах:
class FakeClock {
now() {
return DateTime.fromMillis(1672531200000);
}
}
Использование:
function createService(clock) {
return {
getTimestamp() {
return clock.now().toISO();
}
};
}
Такой подход устраняет зависимость от глобального состояния и позволяет подменять время точечно.
На уровне тестового фреймворка можно управлять системным временем без прямого вмешательства в Luxon.
jest.useFakeTimers();
jest.setSystemTime(new Date("2023-01-01T00:00:00Z"));
После этого:
DateTime.now().toISO(); // будет соответствовать установленному времени
import sinon from "sinon";
const clock = sinon.useFakeTimers({
now: new Date("2023-01-01T00:00:00Z").getTime()
});
После завершения теста:
clock.restore();
Эти методы работают на уровне Date, а значит
автоматически влияют на Luxon без дополнительных настроек.
При одновременном использовании Settings.now и fake
timers важно учитывать приоритет:
Settings.now переопределяет источник времени LuxonDate.now()Если одновременно заданы оба механизма, результат зависит от того, какой источник используется внутри конкретного вызова Luxon. Обычно:
DateTime.now() → учитывает
Settings.nowDate → зависят от fake timersКонфликт этих уровней может приводить к расхождению времени в тестах.
Ещё один способ исключить зависимость от текущего времени — отказ от
now() в пользу явного создания даты:
const dt = DateTime.fromISO("2023-01-01T00:00:00Z");
или
DateTime.fromMillis(1672531200000);
Такой подход полностью устраняет необходимость мокирования, но требует явной передачи времени через систему вызовов.
При фиксации времени важно учитывать влияние временных зон:
DateTime.now().setZone("Europe/Paris");
Если используется глобальный мок времени, зона не изменяет timestamp, но влияет на:
Для стабильных тестов предпочтительно фиксировать не только момент времени, но и зону:
DateTime.fromMillis(1672531200000, { zone: "utc" });
Некорректное управление временем приводит к трудноуловимым багам:
Settings.nowDateTime.now() в одном модуле и
Date.now() в другомnow() в статических
переменныхОсобенно проблемным становится кэширование:
const cached = DateTime.now();
function getTime() {
return cached;
}
В таком случае любые моки времени не влияют на результат, так как значение фиксируется при загрузке модуля.
В сложных системах время рассматривается как зависимость, аналогичная внешнему API или хранилищу данных. Применяется явное внедрение:
function buildService(clock) {
return {
isExpired(timestamp) {
return clock.now().toMillis() > timestamp;
}
};
}
Это полностью устраняет необходимость глобального мокирования Luxon и делает код независимым от системного времени.
Комбинация следующих практик обеспечивает устойчивость:
Settings.now на уровне тестового раннераDateTime.now() в
бизнес-логике