如何在Jest中设置模拟日期?


111

我正在使用moment.js在React组件的帮助器文件中执行大多数日期逻辑,但是我还无法弄清楚如何在Jest a la中模拟日期sinon.useFakeTimers()

Jest文档仅谈论诸如等的计时器功能setTimeoutsetInterval但无助于设置日期,然后检查我的日期功能是否按照其预期的方式工作。

这是我的一些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);
    });

  });

});

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

关于如何实现的任何想法?

Answers:


70

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

之所以表现出色是因为我正在使用Datelike的其他功能valueOf()
罗宾·齐默尔曼

143

由于momentjs在Date内部使用,因此您可以覆盖Date.now函数以始终返回同一时刻。

Date.now = jest.fn(() => 1487076708000) //14.02.2017

要么

Date.now = jest.fn(() => new Date(Date.UTC(2017, 1, 14)).valueOf())

34
这是设置返回实际日期的更漂亮的方法:Date.now = jest.fn(() => new Date(Date.UTC(2017, 0, 1)).valueOf());
开发

4
甚至更漂亮:Date.now = jest.fn(() => +new Date('2017-01-01');
mrzmyr

3
或:Date.now = jest.fn(() => Date.parse('2017-02-14))
杰里米·伊顿

92

jest.spyOn用于锁定时间:

let dateNowSpy;

beforeAll(() => {
    // Lock Time
    dateNowSpy = jest.spyOn(Date, 'now').mockImplementation(() => 1487076708000);
});

afterAll(() => {
    // Unlock Time
    dateNowSpy.mockRestore();
});

3
很好的解决方案;没有依赖关系,并且保持可重置状态使其易于应用于单个测试。
Caleb Miller

14
不需要dateNowSpy变量,mockReset()根据jestjs.io/docs/en/mock-function-api.html#mockfnmockrestore,该变量是多余的。在afterAll,您可以轻松完成Date.now.mockRestore()
Jimmy

这很棒,因此您不需要任何其他库。但这仅在使用静态Date方法(不是很多)的情况下才有效
hellatan

1
@Jimmy Date.now.mockRestore();提供的属性'mockRestore'在类型'()=> number'上不存在错误
Marco Lackovic

3
@Marco应该是jest.spyOn(Date,“ now”)。mockRestore();
sab

6

jest-date-mock是我编写的完整的javascript模块,用于测试jest上的Date。

import { advanceBy, advanceTo } from 'jest-date-mock';

test('usage', () => {
  advanceTo(new Date(2018, 5, 27, 0, 0, 0)); // reset to date time.

  const now = Date.now();

  advanceBy(3000); // advance time 3 seconds
  expect(+new Date() - now).toBe(3000);

  advanceBy(-1000); // advance time -1 second
  expect(+new Date() - now).toBe(2000);

  clear();
  Date.now(); // will got current timestamp
});

仅将3个API用于测试用例。

  • advanceBy(ms):提前日期时间戳,单位为ms。
  • advanceTo([timestamp]):将日期重置为时间戳,默认为0。
  • clear():关闭模拟系统。

你是什​​么情况
atool

5

对于那些想在新的Date对象上模拟方法的人,可以执行以下操作:

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

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

谢谢,这只是解决了我遇到的问题。
格雷森·兰福德

2

所有仅基于模拟的答案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;

2

我想提供一些替代方法。

如果需要存根format()(可能取决于语言环境和时区!)

import moment from "moment";
...
jest.mock("moment");
...
const format = jest.fn(() => 'April 11, 2019')
moment.mockReturnValue({ format })

如果只需要存根moment()

import moment from "moment";
...
jest.mock("moment");
...
const now = "moment(\"2019-04-11T09:44:57.299\")";
moment.mockReturnValue(now);

关于该项测试isDateToday上述功能,我认为最简单的方法是不假moment,在所有


2
对于第一个示例,我得到TypeError: moment.mockReturnValue is not a function
mkelley33 '19

2
jest.mock("moment")在同一水平import语句?否则,欢迎您在项目中
David

1

这就是我嘲笑Date.now()将年份设置为2010年的方法的方式

jest
  .spyOn(global.Date, 'now')
  .mockImplementationOnce(() => new Date(`2010`).valueOf());

1

这是针对不同用例的几种可读方法。我喜欢使用间谍而不是保存对原始对象的引用,这可能会在其他一些代码中意外覆盖。

一次性模拟

jest
  .spyOn(global.Date, 'now')
  .mockImplementationOnce(() => Date.parse('2020-02-14'));

一些测试

let dateSpy;

beforeAll(() => {
  dateSpy = jest
    .spyOn(global.Date, 'now')
    .mockImplementation(() => Date.parse('2020-02-14'));
});

afterAll(() => {
  dateSpy.mockRestore();
});

0

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

// <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

0

目标是在组件渲染过程中使用的固定日期模拟新的Date(),以进行测试。如果您只想模拟new Date()fn,那么使用库将是一项开销。

想法是将全局日期存储到temp变量,模拟全局dae,然后在使用后将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();
    });
});



还要说明您的答案。仅输入代码不是一个好方法
Intsab Haider

1
谢谢你的建议。更新了评论。
Pranava S Balugari

0

我只想在这里打个招呼,因为如果您只想Date在特定套件中模拟对象,那么没有答案可以解决这个问题。

您可以使用每个套件的设置和拆卸方法来模拟它,开玩笑文档

/**
 * Mocking Date for this test suite
 */
const globalDate = Date;

beforeAll(() => {
  // Mocked Date: 2020-01-08
  Date.now = jest.fn(() => new Date(Date.UTC(2020, 0, 8)).valueOf());
});

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

希望这可以帮助!


0

您可以使用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();

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.