Тестирование Date.now

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


Работа с Date.now

Метод Date.now() возвращает количество миллисекунд, прошедших с полуночи 1 января 1970 года по UTC. Функции, использующие Date.now, могут вести себя по-разному в зависимости от времени вызова, что усложняет тестирование.

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


Подмена времени с использованием spyOn

Jasmine предоставляет функцию spyOn, которая позволяет перехватывать вызовы методов и подменять их поведение. Для Date.now это выглядит следующим образом:

describe('Функции с Date.now', function() {
  it('должна возвращать фиксированное время', function() {
    const fixedTime = 1609459200000; // 1 января 2021 года, 00:00:00 UTC
    spyOn(Date, 'now').and.returnValue(fixedTime);

    const result = Date.now();
    expect(result).toBe(fixedTime);
  });
});

Ключевые моменты:

  • spyOn(Date, 'now') создаёт шпион для метода Date.now.
  • and.returnValue(fixedTime) позволяет задать фиксированное значение, возвращаемое при вызове.
  • После теста шпион автоматически снимается, что предотвращает побочные эффекты для других тестов.

Проверка функций, зависящих от времени

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

function isExpired(timestamp) {
  return Date.now() > timestamp;
}

describe('isExpired', function() {
  it('возвращает true, если текущее время больше указанного', function() {
    const futureTime = 1700000000000;
    spyOn(Date, 'now').and.returnValue(1700000001000);
    expect(isExpired(futureTime)).toBeTrue();
  });

  it('возвращает false, если текущее время меньше указанного', function() {
    const futureTime = 1700000001000;
    spyOn(Date, 'now').and.returnValue(1700000000000);
    expect(isExpired(futureTime)).toBeFalse();
  });
});

Особенности:

  • Функция isExpired полностью зависит от времени, поэтому без подмены тесты будут нестабильными.
  • spyOn позволяет контролировать точное значение времени для проверки всех сценариев.

Использование jasmine.clock

Для тестирования таймеров и отложенных действий Jasmine предоставляет встроенные часы:

describe('Таймеры с Date.now', function() {
  beforeEach(function() {
    jasmine.clock().install();
    jasmine.clock().mockDate(new Date(2022, 0, 1)); // 1 января 2022 года
  });

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

  it('проверяет отложенное выполнение', function() {
    let called = false;
    setTimeout(() => { called = true; }, 1000);

    jasmine.clock().tick(1000);
    expect(called).toBeTrue();
  });

  it('позволяет получать текущее время через Date.now', function() {
    expect(Date.now()).toBe(new Date(2022, 0, 1).getTime());
  });
});

Принципы работы с jasmine.clock:

  • install() заменяет внутренние таймеры и методы времени шпионами.
  • mockDate() задаёт фиксированную текущую дату, которая используется всеми методами времени.
  • tick(ms) продвигает виртуальные часы на заданное количество миллисекунд, что полезно для проверки setTimeout и setInterval.
  • uninstall() восстанавливает оригинальные таймеры, предотвращая побочные эффекты.

Подходы к изоляции времени

  1. Подмена конкретных методов: Используется spyOn(Date, 'now'), чтобы фиксировать возвращаемое значение времени.

  2. Виртуальные часы: jasmine.clock() позволяет управлять временем для всех таймеров и вызовов Date.now() одновременно.

  3. Передача времени как параметра: Более чистый способ — передавать timestamp в функции, чтобы тесты не зависели от реального времени.


Практические советы

  • Для юнит-тестов всегда фиксировать время, чтобы тесты были стабильными.
  • Не смешивать реальные и замокированные таймеры в одном наборе тестов.
  • Использовать jasmine.clock() для тестирования отложенных вызовов и анимаций.
  • Для функций, зависящих от времени, рассматривать возможность инъекции зависимости времени, что делает код более тестируемым и чистым.

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