Мокирование системного времени

При тестировании JavaScript-программ часто возникает необходимость работать с системным временем. В реальных приложениях время используется для различных целей — от таймеров и анимаций до вычислений, зависящих от текущей даты. В таких случаях важно уметь “замораживать” или изменять время в тестах, чтобы обеспечить детерминированные и предсказуемые результаты.

Jest предоставляет возможность мокировать время, что позволяет точнее контролировать поведение тестируемых компонентов, зависящих от времени. Рассмотрим, как это реализуется в Jest.

Таймеры в Jest

Для работы с системным временем и таймерами Jest предоставляет несколько полезных функций:

  1. jest.useFakeTimers() — позволяет модулировать таймеры, такие как setTimeout, setInterval и другие.
  2. jest.setSystemTime() — изменяет системное время на заданное в тестах.

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

Мокирование времени с помощью jest.setSystemTime()

Одной из самых мощных функций для тестирования, связанного с временем, является jest.setSystemTime(). С помощью этой функции можно “заморозить” системное время на определённый момент и использовать это время в тестах.

Пример:

jest.setSystemTime(new Date(2021, 0, 1)); // Устанавливаем дату 1 января 2021 года

test('проверка, что дата корректно установлена', () => {
  const currentDate = new Date();
  expect(currentDate.getFullYear()).toBe(2021);
  expect(currentDate.getMonth()).toBe(0); // Январь (0)
});

Применение мокированного времени

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

Пример: тестирование функции, которая возвращает количество дней до ближайшей пятницы:

const getDaysUntilFriday = () => {
  const today = new Date();
  const daysUntilFriday = (5 - today.getDay() + 7) % 7;
  return daysUntilFriday;
};

test('рассчитывает количество дней до пятницы', () => {
  jest.setSystemTime(new Date(2021, 0, 1)); // Пятница, 1 января 2021 года
  expect(getDaysUntilFriday()).toBe(0); // Пятница, следовательно, 0 дней

  jest.setSystemTime(new Date(2021, 0, 4)); // Понедельник, 4 января 2021 года
  expect(getDaysUntilFriday()).toBe(4); // Следующая пятница через 4 дня
});

Использование Fake Timers

Когда требуется тестировать функции, использующие асинхронные таймеры, такие как setTimeout или setInterval, можно воспользоваться функцией jest.useFakeTimers() для симуляции работы таймеров.

Пример с использованием setTimeout:

jest.useFakeTimers();

test('проверка работы setTimeout', () => {
  const mockCallback = jest.fn();

  setTimeout(mockCallback, 1000);

  // До того как таймер сработает, mockCallback не должен быть вызван
  expect(mockCallback).not.toHaveBeenCalled();

  // Прокачиваем таймеры до 1000 мс
  jest.advanceTimersByTime(1000);

  // Теперь mockCallback должен быть вызван
  expect(mockCallback).toHaveBeenCalledTimes(1);
});

Работа с датами и временем в асинхронных тестах

Когда тестируем асинхронные функции, которые зависят от времени (например, с использованием setTimeout, Promise, setInterval), важно правильно использовать мокированные таймеры.

Пример:

jest.useFakeTimers();

const fetchData = (callback) => {
  setTimeout(() => {
    callback('data');
  }, 2000);
};

test('проверка асинхронной функции с использованием setTimeout', () => {
  const mockCallback = jest.fn();

  fetchData(mockCallback);

  // Проверяем, что коллбек ещё не был вызван
  expect(mockCallback).not.toHaveBeenCalled();

  // Прокачиваем таймеры
  jest.advanceTimersByTime(2000);

  // Теперь коллбек должен быть вызван
  expect(mockCallback).toHaveBeenCalledWith('data');
});

Очищение моков

После использования моков времени или таймеров важно не забывать об их сбросе. Jest предоставляет функцию jest.clearAllMocks(), чтобы очистить все моки, и jest.useRealTimers() для возврата к реальным таймерам.

Пример:

afterEach(() => {
  jest.clearAllMocks();
  jest.useRealTimers();
});

Это необходимо для того, чтобы изменения, связанные с моком времени, не влияли на другие тесты, и каждый тест был изолирован.

Мокирование времени с использованием библиотеки mockdate

Для ещё большего контроля над временем можно использовать сторонние библиотеки, такие как mockdate. Эта библиотека позволяет легко задавать конкретные даты и время.

Пример с использованием mockdate:

import MockDate from 'mockdate';

test('проверка работы с библиотекой mockdate', () => {
  MockDate.set('2021-01-01'); // Устанавливаем фиксированную дату

  const currentDate = new Date();
  expect(currentDate.getFullYear()).toBe(2021);
  expect(currentDate.getMonth()).toBe(0);

  MockDate.reset(); // Сбрасываем изменения времени
});

Рекомендации по использованию мокирования времени

  1. Избегайте использования мокированного времени в больших тестах, где оно не является необходимым. Мокирование времени полезно, когда оно влияет на логику работы, но если оно не критично, лучше оставьте реальные таймеры.
  2. Используйте jest.useFakeTimers() для асинхронных функций, чтобы точно контролировать время выполнения.
  3. Не забывайте сбрасывать моки времени в конце каждого теста, чтобы избежать влияния на другие тесты.
  4. Мокируйте только то время, которое необходимо для теста, чтобы тесты оставались читаемыми и понятными.

Таким образом, мокирование времени в Jest — мощный инструмент для точного контроля работы с датами и таймерами, что необходимо для стабильных и предсказуемых тестов.