使用 Date 对象的组件在不同的时区产生不同的快照

Components using Date objects produce different snapshots in different timezones

我正在对我的 React 组件使用 Enzyme with enzyme-to-json to do Jest 快照测试。我正在测试 DateRange 组件的浅快照,该组件呈现具有当前范围的显示字段(例如 5/20/2016 - 7/18/2016)和两个允许选择 Date 值的 DateInput 组件。这意味着我的快照包含我传递给组件的 Dates 在 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}

如何让我的测试在快照中生成相同的 Dates 而不管它们 运行 在哪个时区?

我最终得到了一个由两部分组成的解决方案。

  1. 切勿以依赖时区的方式在测试中创建 Date 对象。如果您不想直接使用时间戳来获得可读的测试代码,请使用 Date.UTC,例如

    new Date(Date.UTC(1995, 4, 23))
    
  2. 模拟用于将 Dates 转换为显示值的日期格式化程序,使其 returns 成为独立于时区的表示,例如使用 Date::toISOString()幸运的是,这对我来说很容易,因为我只需要在我的本地化模块中模拟 formatDate 函数。如果组件以某种方式自行将 Dates 转换为字符串,则可能会更难。

在我找到上述解决方案之前,我试图以某种方式改变快照的创建方式。这很丑陋,因为 enzyme-to-json 保存了 toISOString() 的本地副本,所以我不得不使用 _.cloneDeepWith 并修改所有 Date。无论如何它对我来说都没有用,因为我的测试还包含 Date 从时间戳创建的案例(该组件比我上面描述的要复杂得多)以及它们与我创建的日期之间的交互明确的测试。所以我首先必须确保我所有的日期定义都指的是同一个时区,然后是其他时区。


更新(11/3/2017):最近查看enzyme-to-json时,一直没能找到toISOString()的本地保存,所以可能是这样不再是问题,它可以被嘲笑。不过我也没能在历史上找到它,所以也许我只是错误地注意到是哪个图书馆做的。测试后果自负:)

我最终通过模拟 toLocaleString(或您正在使用的任何 toString 方法)原型来解决这个问题。使用 sinon 我做了:

var toLocaleString;

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

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

通过这种方式,如果您直接从 Date 对象生成字符串,您仍然可以。

我为 hours/days 苦苦挣扎,只有这个对我有用:

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",
    

如果您使用的是 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;
});

This issue 如果您来这里,一定要去看看。

我是通过使用 timezone-mock 做到这一点的,它在内部替换了全局 Date 对象,这是我能找到的最简单的解决方案。

该软件包支持几个测试时区。

import timezoneMock from 'timezone-mock';

describe('when in PT timezone', () => {
  beforeAll(() => {
    timezoneMock.register('US/Pacific');
  });

  afterAll(() => {
    timezoneMock.unregister();
  });

  // ...

https://www.npmjs.com/package/timezone-mock

TZ=UTC 添加到我的 .env 文件解决了我的问题。

一个简单的事实可以使它变得容易。

只需使用:

new Date('some string'). 

这将始终给出无效日期,无论是哪台机器,它始终是无效日期。

干杯。

2020 年适合我的解决方案

beforeEach(() => {
        jest.useFakeTimers('modern');
        jest.setSystemTime(Date.parse(FIXED_SYSTEM_TIME));
});

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