Skip to content

Feature Mock clock

github-actions[bot] edited this page Jul 14, 2026 · 1 revision

🧪 Тестовые часы — MockClock

← Возможности · Главная · Сравнение

Всё, что умеет FrozenClock, плюс виртуальный сон: sleep() мгновенно сдвигает время вместо блокировки процесса.

Принцип работы

MockClock реализует одновременно AdjustableClockInterface и SleepableClockInterface. Код, который в проде получает SystemClock и честно спит, в тестах получает MockClock — и «спит» за микросекунды:

use CloudCastle\Clock\MockClock;

$clock = MockClock::from('2026-01-01 00:00:00', 'UTC');

$clock->sleep(3600.0);        // мгновенно: время +1 час, процесс не ждёт
$clock->sleepUntil(new DateTimeImmutable('2026-01-01 06:00:00', new DateTimeZone('UTC')));

$clock->now()->format('H:i'); // 06:00

sleepUntil() с прошедшим или текущим моментом ничего не меняет — включая экземпляр момента: часы не подменяют его на переданный.

Тестирование retry/backoff

final class ApiRetrier
{
    public function __construct(private readonly SleepableClockInterface $clock) {}

    public function call(callable $attempt): mixed
    {
        foreach ([1.0, 2.0, 4.0] as $backoff) {
            try {
                return $attempt();
            } catch (TransportException) {
                $this->clock->sleep($backoff);
            }
        }

        return $attempt();
    }
}

// В тесте: MockClock — три ретрая проходят мгновенно,
// и можно проверить, что виртуально прошло ровно 7 секунд.

Плюсы

  • Тесты retry/backoff/тайм-аутов выполняются за миллисекунды.
  • Паритет семантики sleep()/modify() с Symfony\Component\Clock\MockClock подтверждён интеграционными тестами пакета.

Минусы

  • Виртуальный сон не отдаёт управление другим процессам — для интеграционных тестов с реальной конкуренцией используйте SystemClock и короткие паузы.

✅ Рекомендуется

  • Единственные часы в юнит-тестах кода с ожиданиями.
  • Совместно с ClockHolder — подмена времени всего приложения одной строкой.

❌ Антипаттерны

  • Реальный usleep() в тестах «для надёжности» рядом с MockClock — вы теряете весь выигрыш.

Схема фичи

flowchart LR
    MC[MockClock] --- A[AdjustableClockInterface]
    MC --- S[SleepableClockInterface]
    A -->|"setTo/modify/advance/rewind"| VT[(виртуальное время)]
    S -->|"sleep()/sleepUntil() — без блокировки"| VT
Loading

Жизненный цикл в тесте

sequenceDiagram
    participant T as Тест
    participant C as Код с backoff
    participant M as MockClock

    T->>M: from('2026-01-01', 'UTC')
    T->>C: run(clock: M)
    loop ретраи 1с, 2с, 4с
        C->>M: sleep(N) — мгновенно
        M->>M: время += N
    end
    C-->>T: результат за миллисекунды
    T->>M: now() == 00:00:07 ✓
Loading

Сравнение с аналогами

Возможность Clock symfony/clock lcobucci/clock beste/clock nesbot/carbon ergebnis/clock 🏆 Победитель
Виртуальный sleep() Clock + symfony/clock
Виртуальный sleepUntil(момент) Clock
Полный набор setTo/advance/rewind ⚠️ ⚠️ Clock
Отдельный тип для тестовых часов Clock + symfony/clock

Clone this wiki locally