Работа с текущим временем в тестах создаёт фундаментальную нестабильность: результат выполнения зависит от момента запуска, что приводит к «плавающим» тестам. Любые конструкции вида:
LocalDate.now()
Instant.now()
ZonedDateTime.now()
привязывают бизнес-логику к системным часам, делая поведение непредсказуемым.
Основные последствия:
Библиотека js-joda решает эту проблему через абстракцию
Clock, позволяющую подменять источник времени.
В основе механизма лежит концепция часов как отдельного объекта, а не глобального состояния системы.
Clock в js-joda:
Основные фабрики:
Clock.systemUTC()Clock.system(ZoneId)Clock.fixed(Instant, ZoneId)Clock.offset(baseClock, Duration)Clock.fixed создаёт неизменяемые часы, возвращающие одно
и то же значение времени независимо от вызовов.
Сигнатура:
Clock.fixed(instant, zone)
Параметры:
instant — точка времени (Instant)zone — часовой пояс (ZoneId)import { Clock, Instant, ZoneId, LocalDate } from '@js-joda/core';
const fixedInstant = Instant.parse('2024-01-01T00:00:00Z');
const clock = Clock.fixed(fixedInstant, ZoneId.UTC);
const date = LocalDate.now(clock);
console.log(date.toString()); // 2024-01-01
Результат полностью детерминирован: независимо от момента запуска теста, дата всегда одинакова.
Ключевая практика — передача Clock через зависимости, а
не использование now() напрямую.
class OrderService {
createOrder() {
return {
createdAt: Instant.now()
};
}
}
Проблема — невозможность контроля времени.
import { Instant } from '@js-joda/core';
class OrderService {
constructor(clock) {
this.clock = clock;
}
createOrder() {
return {
createdAt: Instant.now(this.clock)
};
}
}
import { Clock, Instant, ZoneId } from '@js-joda/core';
test('order has deterministic creation time', () => {
const fixedClock = Clock.fixed(
Instant.parse('2025-06-01T12:00:00Z'),
ZoneId.UTC
);
const service = new OrderService(fixedClock);
const order = service.createOrder();
expect(order.createdAt.toString())
.toBe('2025-06-01T12:00:00Z');
});
При использовании временных зон важно учитывать, что
Clock.fixed фиксирует:
Instant)ZoneId)import { ZonedDateTime, Clock, Instant, ZoneId } from '@js-joda/core';
const clock = Clock.fixed(
Instant.parse('2024-03-10T10:00:00Z'),
ZoneId.of('Europe/Paris')
);
const zdt = ZonedDateTime.now(clock);
Результат зависит от зоны, но момент времени остаётся неизменным.
Fixed Clock особенно полезен для сценариев:
const clock = Clock.fixed(
Instant.parse('2024-01-31T23:59:59Z'),
ZoneId.UTC
);
const clock = Clock.fixed(
Instant.parse('2024-02-29T10:00:00Z'),
ZoneId.UTC
);
const clock = Clock.fixed(
Instant.parse('2024-03-31T01:30:00Z'),
ZoneId.of('Europe/Berlin')
);
| Характеристика | System Clock | Fixed Clock |
|---|---|---|
| Изменяется со временем | Да | Нет |
| Подходит для production | Да | Нет |
| Подходит для тестов | Нет | Да |
| Повторяемость | Нет | Да |
| Воспроизводимость багов | Сложная | Простая |
class TokenService {
constructor(clock) {
this.clock = clock;
}
isExpired(expirationInstant) {
const now = Instant.now(this.clock);
return now.isAfter(expirationInstant);
}
}
Иногда требуется моделировать «время после события».
import { Clock, Duration } from '@js-joda/core';
const baseClock = Clock.fixed(
Instant.parse('2024-01-01T00:00:00Z'),
ZoneId.UTC
);
const futureClock = Clock.offset(baseClock, Duration.ofHours(5));
Поведение:
baseClock — фиксированная точкаfutureClock — сдвинутое времяInstant.now(); // плохо в тестируемом коде
Instant.now(clock); // правильно
const now = Instant.parse('2024-01-01T00:00:00Z');
Такой подход ломает гибкость тестов.
Clock.fixed(instant, ZoneId.UTC);
При тестировании локализованного поведения зона должна быть частью сценария.
Оптимальная структура:
const appClock = Clock.systemUTC();
const service = new OrderService(appClock);
Использование Fixed Clock позволяет:
const startClock = Clock.fixed(
Instant.parse('2024-01-01T00:00:00Z'),
ZoneId.UTC
);
const dayLater = Clock.offset(startClock, Duration.ofDays(1));
Использование Fixed Clock способствует:
Вся система времени в js-joda может быть сведена к одному принципу:
Fixed Clock превращает временную ось из внешнего фактора в управляемую переменную тестовой среды, обеспечивая полную детерминированность вычислений над temporal-объектами.