Компоненты, использующие объекты Date, создают разные моментальные снимки в разных часовых поясах

Я использую Enzyme с энзимом к json для Jest тестирования снимков моих компонентов React. Я тестирую мелкие снимки компонента DateRange, который отображает поле отображения с текущим диапазоном (например, 5/20/2016 - 7/18/2016) и два компонента DateInput, которые позволяют выбрать значение Date. Это означает, что мой снимок содержит Date, который я передаю компоненту как в подпорках DateInput, так и в текстовом представлении, которое он разрешает сам. В моем тесте я создаю несколько фиксированных дат, используя new Date(1995, 4, 23).

Когда я запускаю тест в разных часовых поясах, это создает разные снимки, потому что конструктор Date(year, month, ...) создает дату в местном часовом поясе. Например. использование new Date() создает эту разницу в снимке между запусками в моем местном часовом поясе и на нашем CI-сервере.

- value={1995-05-22T22:00:00.000Z}
+ value={1995-05-23T00:00:00.000Z}

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

- value={5/20/2016 - 7/18/2016}
+ value={5/19/2016 - 7/17/2016}

Как я могу заставить мои тесты создавать одинаковые Date на снимках независимо от часового пояса, в котором они выполняются?

Ответ 1

Я боролся с этим часами/днями, и только это работало для меня:

1) В вашем тесте:

Date.now = jest.fn(() => new Date(Date.UTC(2017, 7, 9, 8)).valueOf())

2) Затем измените TZ env var перед запуском ваших тестов. Итак, скрипт в моем package.json:

  • (Только для Mac и Linux)

    "test": "TZ=America/New_York react-scripts test --env=jsdom",
    
  • (Windows)

    "test": "set TZ=America/New_York && react-scripts test --env=jsdom",
    

Ответ 2

В итоге у меня было решение, состоящее из двух частей.

  • Никогда не создавайте объекты Date в тестах в зависимости от часового пояса. Если вы не хотите использовать временные метки непосредственно для считывания тестового кода, используйте Date.UTC, например.

    new Date(Date.UTC(1995, 4, 23))
    
  • Измените формат даты, используемый для преобразования Date в отображаемые значения, чтобы он возвращал независимое от часового пояса представление, например. используйте Date::toISOString(). К счастью, это было легко в моем случае, поскольку мне просто нужно было высмеять функцию formatDate в моем модуле локализации. Это может быть сложнее, если компонент каким-то образом превращает Date в строки самостоятельно.

Прежде чем я пришел к вышеуказанному решению, я попытался как-то изменить способ создания моментальных снимков. Это было уродливо, потому что фермент-json сохраняет локальную копию toISOString(), поэтому мне пришлось использовать _.cloneDeepWith и изменить все Date s. В любом случае это не сработало для меня, потому что в моих тестах также содержались случаи создания Date из временных меток (компонент довольно сложный, чем описан выше) и взаимодействия между теми и датами, которые я создавал в тестах в явном виде. Поэтому я сначала должен был убедиться, что все мои определения даты относятся к одному и тому же часовому поясу, а остальные следуют.


Обновление (11/3/2017): когда я недавно проверил enzyme-to-json, мне не удалось найти локальную экономию toISOString(), поэтому, возможно, это уже не проблема, и ее можно было высмеять. Я не смог найти его в истории, хотя, возможно, я просто неправильно заметил, какая библиотека это сделала. Испытайте свою собственную опасность:)

Ответ 3

Я закончил тем, что обошел это, издеваясь над прототипом toLocaleString (или любого другого метода toString, который вы используете). Используя sinon, я сделал:

var toLocaleString;

beforeAll(() => {
    toLocaleString = sinon.stub(Date.prototype, 'toLocaleString', () => 'fake time')
})

afterAll(() => {
    toLocaleString.restore()
})

Таким образом, если вы генерируете строки прямо из объекта Date, вы все еще в порядке.

Ответ 4

Я наткнулся на самое простое решение - просто передайте Date строку, и часовой пояс не будет иметь значения, если вам нужно только правильное представление строки.

Date("2022-02-22") всегда будет представлять '2022-02-22T00:00:00.000Z' в часовом поясе, в котором вы работаете, узел.

Это будет выглядеть следующим образом в вашем блоке, который вы запускаете перед тестами, если дата генерируется автоматически с помощью new Date():

  const baseTime = new Date("2022-02-22");
  spyOn(global, 'Date').and.returnValue(baseTime);

Ответ 5

Если вы используете конструктор new Date() вместо Date.now, вы можете сделать следующее:

const RealDate = Date;

beforeEach(() => {
  // @ts-ignore
  global.Date = class extends RealDate {
    constructor() {
      super();
      return new RealDate("2016");
    }
  };
})
afterEach(() => {
  global.Date = RealDate;
});

Этот вопрос обязательно нужно посетить, если вы здесь.