我使用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对象上模拟方法的人,您可以执行以下操作:

beforeEach(() => {
    jest.spyOn(Date.prototype, 'getDay').mockReturnValue(2);
    jest.spyOn(Date.prototype, 'toISOString').mockReturnValue('2000-01-01T00:00:00.000Z');
});

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

其他回答

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();

目标是在组件呈现期间为测试目的而使用的任何地方,都使用固定日期模拟new Date()。如果您只想模拟new Date() fn,那么使用库将是一项开销。

想法是将全局日期存储到一个临时变量中,模拟全局日期,然后在使用后将temp重新分配给全局日期。

export const stubbifyDate = (mockedDate: Date) => {
    /**
     * Set Date to a new Variable
     */
    const MockedRealDate = global.Date;

    /**
     *  Mock Real date with the date passed from the test
     */
    (global.Date as any) = class extends MockedRealDate {
        constructor() {
            super()
            return new MockedRealDate(mockedDate)
        }
    }

    /**
     * Reset global.Date to original Date (MockedRealDate) after every test
     */
    afterEach(() => {
        global.Date = MockedRealDate
    })
}

Usage in your test would be like

import { stubbyifyDate } from './AboveMethodImplementedFile'

describe('<YourComponent />', () => {
    it('renders and matches snapshot', () => {
        const date = new Date('2019-02-18')
        stubbifyDate(date)

        const component = renderer.create(
            <YourComponent data={}/>
        );
        const tree = component.toJSON();
        expect(tree).toMatchSnapshot();
    });
});


你可以用date-faker。允许您相对地更改当前日期:

import { dateFaker } from 'date-faker';
// or require if you wish: var { dateFaker } = require('date-faker');

// make current date to be tomorrow
dateFaker.add(1, 'day'); // 'year' | 'month' | 'day' | 'hour' | 'minute' | 'second' | 'millisecond'.

// change using many units
dateFaker.add({ year: 1, month: -2, day: 3 });

// set specific date, type: Date or string
dateFaker.set('2019/01/24');

// reset
dateFaker.reset();

我推荐sinonjs/fake-timers。它与jest提供的假计时器非常相似,但用户友好得多。

import FakeTimers from '@sinonjs/fake-timers';

const clock = FakeTimers.install()
clock.setSystemTime(new Date('2022-01-01'));

console.log(new Date()) // 2020-01-01T00:00:00.000Z

下面的测试存根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);
  });
});

这对我很管用。