首页 > 解决方案 > moment.js 模拟 local() 所以单元测试运行一致

问题描述

我想测试以下代码。我想知道是否有办法模拟moment.js或强制它认为我当前的位置是America/New_York这样我的单元测试不会在可能位于不同地理位置的 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返回一个一致的值。

标签: javascriptjestjsmomentjs

解决方案


问题

所以这个问题没有一个单一的答案。这个问题是 javascript 的一个基本问题,您可以通过以下两种方式之一查看日期:

  1. UTC(getUTCHours()getUTCMinutes()
  2. 本地(即系统getHours()getMinutes()

并且没有指定的方法来设置有效的系统时区,甚至是 UTC 偏移量。

(浏览mdnDate参考资料或查看规范以了解这一切有多么无用。)

“可是等等!” 我们哭了,“这不就是moment-timezone存在的理由吗??”

不完全是。momentmoment-timezone更好/更轻松地控制 javascript 中的时间管理,但即使他们也无法知道本地时区Date正在使用什么,并使用其他机制来了解这一点。这是一个如下问题。

一旦您了解了代码,您会发现 moment 的 moment.local()方法(原型声明setOffsetToLocal的实现)有效地执行了以下操作:

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

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

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

_dDate时刻 ( mom) 包裹的对象。因此,对于非 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()应该允许您访问日期时间的属性,就好像它认为本地时区是在moment-timezone.

我想过尝试修补moment,但它比 . 复杂得多Date,而且修补Date应该是健壮的,因为它是原始的。


推荐阅读