Концепция mock clock

Mock Clock — это инструмент в Jasmine, позволяющий контролировать время в тестах, симулируя работу таймеров (setTimeout, setInterval, Date) без необходимости реального ожидания. Он используется для ускорения тестов и повышения их предсказуемости, особенно когда требуется проверка асинхронного кода, зависящего от времени.

Подключение и активация Mock Clock

Для работы с mock clock используется метод jasmine.clock(). Основные шаги:

  1. Подключение к Jasmine clock:
beforeEach(function() {
    jasmine.clock().install();
});

afterEach(function() {
    jasmine.clock().uninstall();
});

Методы install() и uninstall() гарантируют, что mock clock активен только на время конкретного теста, предотвращая влияние на другие тесты.

  1. Инициализация времени: При необходимости можно установить фиксированное текущее время:
const baseTime = new Date(2026, 0, 11);
jasmine.clock().mockDate(baseTime);

Все вызовы new Date() будут возвращать это значение, что обеспечивает предсказуемость тестов, зависящих от времени.


Работа с таймерами

После установки mock clock стандартные таймеры становятся управляемыми:

  • setTimeout:
let result = false;
setTimeout(() => { result = true; }, 1000);

jasmine.clock().tick(1000); // имитация 1000 мс
console.log(result); // true

Метод tick(ms) ускоряет время на указанное количество миллисекунд, вызывая все таймеры, срок которых истек.

  • setInterval:
let counter = 0;
setInterval(() => { counter++; }, 500);

jasmine.clock().tick(1500);
console.log(counter); // 3

Каждый интервал выполняется столько раз, сколько раз уложилось в указанное время при tick.


Mock Clock и асинхронные функции

Для тестирования промисов и асинхронных операций mock clock может использоваться в сочетании с done колбэком:

it("должен изменять состояние через 1 секунду", function(done) {
    let status = false;
    setTimeout(() => { status = true; }, 1000);

    jasmine.clock().tick(1000);
    expect(status).toBeTrue();
    done();
});

Важно помнить, что mock clock не управляет промисами, созданными через Promise.resolve().then(...), так как они обрабатываются микротасками вне таймеров. Для таких случаев используют либо реальные таймеры, либо дополнительные методы синхронизации.


Замена текущей даты

Метод mockDate позволяет фиксировать время, что полезно для тестов, зависящих от системного времени:

const fixedDate = new Date(2026, 0, 11, 12, 0, 0);
jasmine.clock().mockDate(fixedDate);

expect(new Date().getHours()).toBe(12);

Это предотвращает непредсказуемое поведение, если тесты выполняются в разное время суток или в разных часовых поясах.


Совместное использование с другими spy

Jasmine позволяет комбинировать mock clock с spyOn для отслеживания вызовов функций внутри таймеров:

const callback = jasmine.createSpy("callback");
setTimeout(callback, 1000);

jasmine.clock().tick(1000);
expect(callback).toHaveBeenCalled();

Такой подход упрощает проверку, что определенные функции вызываются через заданный интервал времени, без реальной задержки.


Ограничения и особенности

  1. Нет управления микротасками: Promise.then и async/await не управляются jasmine.clock(). Для их тестирования требуется отдельное ожидание через done или использование async/await с реальными таймерами.
  2. Установка mockDate заменяет Date глобально, поэтому любые операции с датой внутри теста будут использовать фиксированное значение.
  3. Необходимо вызывать uninstall() в afterEach, иначе mock clock может «утечь» в другие тесты, вызывая неожиданное поведение.

Практическая польза

Mock clock особенно эффективен в следующих сценариях:

  • Проверка поведения таймеров без фактической задержки.
  • Тестирование повторяющихся событий через setInterval.
  • Проверка функционала, зависящего от текущей даты и времени.
  • Ускорение больших наборов тестов, где реальные таймеры замедляли бы выполнение.

Использование mock clock повышает детерминированность тестов, делает их быстрее и устойчивыми к случайным задержкам, что критично при CI/CD.