我使用moment.js在我的React组件的帮助文件中做大部分的日期逻辑,但我还没有弄清楚如何在Jest a la sinon.useFakeTimers()中模拟日期。

Jest文档只谈到了计时器函数,如setTimeout, setInterval等,但没有帮助设置日期,然后检查我的日期函数是否做了他们应该做的事情。

这是我的一些JS文件:

var moment = require('moment');

var DateHelper = {
  
  DATE_FORMAT: 'MMMM D',
  API_DATE_FORMAT: 'YYYY-MM-DD',
  
  formatDate: function(date) {
    return date.format(this.DATE_FORMAT);
  },

  isDateToday: function(date) {
    return this.formatDate(date) === this.formatDate(moment());
  }
};


module.exports = DateHelper;

这是我用Jest设置的:

jest.dontMock('../../../dashboard/calendar/date-helper')
    .dontMock('moment');

describe('DateHelper', function() {
  var DateHelper = require('../../../dashboard/calendar/date-helper'),
      moment = require('moment'),
      DATE_FORMAT = 'MMMM D';

  describe('formatDate', function() {

    it('should return the date formatted as DATE_FORMAT', function() {
      var unformattedDate = moment('2014-05-12T00:00:00.000Z'),
          formattedDate = DateHelper.formatDate(unformattedDate);

      expect(formattedDate).toEqual('May 12');
    });

  });

  describe('isDateToday', function() {

    it('should return true if the passed in date is today', function() {
      var today = moment();

      expect(DateHelper.isDateToday(today)).toEqual(true);
    });
    
  });

});

现在这些测试通过了,因为我使用了moment,我的函数也使用了moment,但它似乎有点不稳定,我想将日期设置为测试的固定时间。

你知道该怎么做吗?


当前回答

下面的测试存根Date在测试生命周期中返回一个常量。

如果你在你的项目中使用了new Date(),那么你可以在你的测试文件中模拟它,如下所示:

  beforeEach(async () => {
    let time_now = Date.now();
    const _GLOBAL: any = global;
    _GLOBAL.Date = class {
      public static now() {
        return time_now;
      }
    };
}

现在,无论您在测试文件中使用new Date(),它都会产生相同的时间戳。

注意:你可以用beforeAll替换beforeEach。而_GLOBAL只是一个满足typescript的代理变量。

完整的代码我尝试:

let time_now;
const realDate = Date;

describe("Stubbed Date", () => {
  beforeAll(() => {
    timeNow = Date.now();
    const _GLOBAL: any = global;
    _GLOBAL.Date = class {
      public static now() {
        return time_now;
      }

      constructor() {
        return time_now;
      }

      public valueOf() {
        return time_now;
      }
    };
  });

  afterAll(() => {
    global.Date = realDate;
  });

  it("should give same timestamp", () => {
    const date1 = Date.now();
    const date2 = new Date();
    expect(date1).toEqual(date2);
    expect(date2).toEqual(time_now);
  });
});

这对我很管用。

其他回答

MockDate可以在玩笑测试中使用,以改变new Date()返回的内容:

var MockDate = require('mockdate');
// I use a timestamp to make sure the date stays fixed to the ms
MockDate.set(1434319925275);
// test code here
// reset to native Date()
MockDate.reset();

我用的是moment + moment-timezone,这些都不适合我。

这工作:

jest.mock('moment', () => {
  const moment = jest.requireActual('moment');
  moment.now = () => +new Date('2022-01-18T12:33:37.000Z');
  return moment;
});

仅基于Date.now()的模拟的所有答案不会在任何地方都适用,因为一些包(例如moment.js)使用new Date()代替。

在这种情况下,基于MockDate的答案是我认为唯一真正正确的答案。如果你不想使用外部包,你可以直接在你的beforeAll中写:

  const DATE_TO_USE = new Date('2017-02-02T12:54:59.218Z');
  // eslint-disable-next-line no-underscore-dangle
  const _Date = Date;
  const MockDate = (...args) => {
    switch (args.length) {
      case 0:
        return DATE_TO_USE;
      default:
        return new _Date(...args);
    }
  };
  MockDate.UTC = _Date.UTC;
  MockDate.now = () => DATE_TO_USE.getTime();
  MockDate.parse = _Date.parse;
  MockDate.toString = _Date.toString;
  MockDate.prototype = _Date.prototype;
  global.Date = MockDate;

我发现最好的方法就是用你正在使用的函数重写原型。

Date.prototype.getTimezoneOffset = function () {
   return 456;
};

Date.prototype.getTime = function () {
      return 123456;
};

我想使用手动模拟,所以它可以在所有测试中使用。

// <rootDir>/__mocks__/moment.js
const moment = jest.requireActual('moment')

Date.now = jest.fn(() => 1558281600000) // 2019-05-20 00:00:00.000+08:00

module.exports = moment