Jest.js moment.js模拟local(),以便单元测试一致运行

busg9geu  于 2023-02-10  发布在  Jest
关注(0)|答案(3)|浏览(181)

我想测试下面的代码。我想知道是否有一种方法可以模拟moment.js或强制它认为我的当前位置是America/New_York,这样我的单元测试就不会在www.example.com runner中失败gitlab.ci,因为它可能位于不同的地理位置?

const centralTimeStartOfDay = moment.tz('America/Chicago').startOf('day');
  const startHour = centralTimeStartOfDay
    .hour(7)
    .local()
    .hour();

基本上,我希望硬编码我的时区为America/New_York,并希望此函数的行为一致。

编辑

我试过:

  1. Date.now = () => new Date("2020-06-21T12:21:27-04:00")
  2. moment.tz.setDefault('America/New_York')
    我仍然得到了相同的结果,我想模拟当前时间,这样startHour就能返回一个一致的值。
vtwuwzda

vtwuwzda1#

问题是

所以这个问题没有一行字的答案,这个问题是javascript的一个基本问题,你可以用两种方式之一查看日期:
1.协调世界时(一月一日、一月一日等)
1.本地(即系统、getHours()getMinutes()等)
而且没有指定的方法来设置有效的系统时区,甚至UTC偏移量。
(Scan通过mdn Date reference或检查规范,以了解这一切是多么没有帮助。)
"等等!"我们喊道,"这不就是moment-timezone存在的原因吗?"
不完全是这样,momentmoment-timezone在javascript中提供了更好/更容易的时间管理控制,但即使是它们也无法知道Date使用的是什么本地时区,并使用其他机制来了解它。
了解了代码之后,您将看到moment的.local()方法(setOffsetToLocal的原型声明和实现)有效地执行了以下操作:

  • 将时刻的UTC偏移设置为0
  • 通过将_isUTC设置为false禁用"UTC模式"。

禁用"UTC模式"的效果是意味着大多数访问器方法被转发到底层的Date对象。例如.hours()最终调用moment/get-set. js get(),如下所示:

export function get(mom, unit) {
    return mom.isValid()
        ? mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]()
        : NaN;
}

_d是时刻(mom)正在 Package 的Date对象。因此,对于非UTC模式时刻,moment.hours()有效地传递到Date.prototype.getHours()。您用moment.tz.setDefault()设置了什么,或者是否覆盖了Date.now(),都不重要。
还有一件事...
你说:
基本上,我希望将时间硬编码为America/New_York,并希望此函数的行为保持一致
但实际上,这通常是不可能的。你使用的是芝加哥,我想芝加哥与纽约同步抵消了时差,但例如,英国时差与美国时差不同,所以如果你从美国时区转换到英国时区,一年中会有几周测试失败。

解决方案。

但这仍然令人沮丧,因为我不希望我在波兰和美国西海岸的开发人员因为我的CI服务器在UTC运行而破坏本地测试。
第一个解决方案不是一个解决方案:找到一种不同的方法来做你正在做的事情!通常使用.local()的用例是相当有限的,并且是向用户显示他们的 * 当前偏移量 * 中的时间。它甚至不是他们的时区,因为本地Date方法只会查看当前偏移量。所以大多数时候你只想使用它来表示当前时间,或者如果您不介意它对一半的Date对象是错误的(对于使用夏令时的时区),那么通过其他方式了解用户想要的时区可能会更好,而根本不使用.local()
第二个解决方案也不是一个解决方案:别太担心你的测试!显示本地时间的主要问题是它能正常工作,你并不关心它到底是什么。手动验证它显示的时间是否正确,在测试中验证它返回的时间是否合理,而不检查具体的时间。
如果你还想继续,最后一个解决方案至少可以让你的案例和其他一些案例工作,如果你发现你需要扩展它,你需要做什么是显而易见的。然而,这是一个复杂的领域,我不保证这不会有一些意想不到的副作用!
在测试设置文件中:

[
  'Date',
  'Day',
  'FullYear',
  'Hours',
  'Minutes',
  'Month',
  'Seconds',
].forEach(
  (prop) => {
    Date.prototype[`get${prop}`] = function () {
      return new Date(
        this.getTime()
        + moment(this.getTime()).utcOffset() * 60000
      )[`getUTC${prop}`]();
    };
  }
);

现在您应该能够使用moment.tz.setDefault(),并且使用.local()应该允许您访问datetime的属性,就好像它认为本地时区是在moment-timezone中配置的一样。
我想过尝试修补moment,但它比Date复杂得多,修补Date应该是健壮的,因为它是原始的。

tag5nh1u

tag5nh1u2#

尝试

// package.json
{
  "scripts": {
    "test": "TZ=EST jest"
  }
}
laik7k3q

laik7k3q3#

非常棒的daphtdazz -谢谢!为了向读者说明,这是我用来控制当前日期、时区的完整解决方案,在daphtdazz的帮助下-local()行为in moment:

import MockDate from 'mockdate';

const date = new Date('2000-01-01T02:00:00.000+02:00');
MockDate.set(date);

[
    'Date',
    'Day',
    'FullYear',
    'Hours',
    'Minutes',
    'Month',
    'Seconds',
].forEach(
    (prop) => {
        Date.prototype[`get${prop}`] = function () {
            return new Date(
                this.getTime()
                + moment(this.getTime()).utcOffset() * 60000
            )[`getUTC${prop}`]();
        };
    }
);

const moment = require.requireActual('moment-timezone');
jest.doMock('moment', () => {
    moment.tz.setDefault('Africa/Maputo');
    return moment;
});

相关问题