Тестирование дат и часовых поясов с помощью Jest

Без установки глобальной переменной среды TZ и возможности подделки текущего системного времени для обеспечения стабильности тестов

Предисловие

Уже есть много сообщений, посвященных тестированию дат и часовых поясов в JavaScript, но все те, с которыми я столкнулся, были либо слишком простыми, либо слишком грубыми (запуск всех тестов с переменной окружения TZ), либо не касались имитации обоих дата и часовой пояс.

Даты — это сложно, а часовые пояса — сложнее, особенно когда это делается на JavaScript. Есть несколько библиотек для обхода трудностей использования Date и предложение заменить его на что-то более современное.

Их тестирование также может быть затруднено, так как вы можете захотеть протестировать пограничные случаи (например, високосный год) или несколько часовых поясов. Большинство решений этой проблемы в экосистеме JavaScript фокусируются на одной из трех вещей:

  1. Подделка системного времени и возможность вручную управлять им и запускать таймеры (например, setTimeout, setInterval и т. д.). Не дожидаясь фактического указанного времени, чтобы пройти.
  2. «Блокировка» системного времени до некоторого заранее определенного значения, чтобы вы могли выполнять на нем свои тесты. Например, если вы пишете средство выбора даты, вы можете захотеть «привязать» время к заданной дате, а затем убедиться, что средство выбора ведет себя так, как ожидалось, в это время. В противном случае о ваших expect становится сложнее рассуждать, поскольку они динамичны по своей природе.
  3. Изменение часового пояса системы, чтобы вы могли проверить, как ваш код ведет себя в разных средах. Это особенно актуально для веб-приложений, где вы не можете контролировать среду, в которой выполняется код, поскольку он выполняется на компьютерах пользователей.

У каждой из этих проблем есть несколько изолированных решений в виде пакетов npm или способов запуска ваших тестов:

Ручное управление таймерами

Jest имеет довольно хорошие встроенные мокаторы таймера, и фальшивые таймеры Sinon.js тоже очень хороши (и, по моему опыту, проще в использовании).

См. их документы для примеров, там есть что охватить, и они отлично справляются с этим!

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

Для блокировки системного времени вы можете использовать mockdate, который позволяет указать дату-время в тесте, после чего последующие использования Date будут использовать это время:

Подделка часового пояса

Есть два основных подхода к решению вопроса подделки часового пояса в тестах:

  1. Установите переменную окружения TZ перед запуском Jest:
TZ=UTC jest

Это приведет к тому, что все тесты будут выполняться в часовом поясе UTC. Независимо от того, связан ли тест с часовым поясом или нет. Это еще большая проблема, если вы хотите протестировать несколько часовых поясов. Вам придется прибегнуть к таким решениям, как:

TZ=UTC jest && TZ=America/New_York jest

(или запускать их одновременно, используя что-то вроде concurrently), но опять же — даже если только несколько ваших тестов требуют тестирования часового пояса, вы запускаете их все для всех часовых поясов, а также сталкиваетесь с тем, что тестовый код не знает часового пояса, в котором он работает.

2. Используйте такой пакет, как timezoned-date, который позволяет создать новый конструктор Dateс динамическим offset:

это прекрасно работает, просто не забудьте сохранить исходный Date перед его переопределением и восстановить его позже:

Блокировка системного времени И установка часового пояса

Совместное использование mockdate и timezoned-date создает проблему, которая может быть неочевидной — поскольку оба (прямо или косвенно) переопределяют глобальный Date, важна реализация — особенно в случае mockdate, который переопределяет сброс самого исходного Date. На момент написания этой статьи реализация сохраняет Date при первом require -ed модуля:

// mockdate.ts
const RealDate = Date;


Это означает, что этот код не будет вести себя так, как ожидалось:

Как видите, mockdate работал нормально, но не учитывал Date конструктор, который мы только что создали и переопределили с помощью timezoned-date.

Чтобы исправить это, мы можем создать тестовую служебную функцию, которая решает проблему, абстрагируя проблемную реализацию, а также упрощает тестовый код:

Тогда использование довольно простое:

Заключение

Я надеюсь, что этот пост помог вам лучше понять различные способы проверки дат в JavaScript, особенно когда вы используете сторонние модули, которые полагаются на глобальные переменные и не внедряют свои зависимости (например, используя Date).

Реализация setupMockDate, представленная выше, должна быть достаточно общей, чтобы охватывать различные варианты использования, и может быть полезной, если ее опубликовать напрямую в npm, чтобы избавить вас от необходимости копировать и вставлять это в свою кодовую базу. Если я опубликую это, я обязательно обновлю этот пост в блоге.