簡體   English   中英

如何在 Jest 中設置模擬日期?

[英]How do I set a mock date in Jest?

我正在使用 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);
    });
    
  });

});

現在這些測試通過了,因為我正在使用 moment 並且我的函數使用 moment 但它似乎有點不穩定,我想將日期設置為測試的固定時間。

關於如何實現的任何想法?

由於 momentjs 在內部使用Date ,您可以覆蓋Date.now函數以始終返回相同的時刻。

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

或者

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

jest.spyOn適用於鎖定時間:

let dateNowSpy;

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

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

從 Jest 26 開始,這可以使用“現代”假定時器來實現,而無需安裝任何 3rd 方模塊: https : //jestjs.io/blog/2020/05/05/jest-26#new-fake-timers

jest
  .useFakeTimers('modern')
  .setSystemTime(new Date('2020-01-01').getTime());

如果您希望所有測試的假計時器都處於活動狀態,您可以在您的配置中設置timers: 'modern'https : //jestjs.io/docs/en/configuration#timers-string

MockDate可用於Jest測試以更改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對象上模擬方法的人,您可以執行以下操作:

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

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

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():關閉模擬系統。

以下是針對不同用例的幾種可讀方式。 我更喜歡使用間諜而不是保存對原始對象的引用,原始對象可能會被其他一些代碼意外覆蓋。

一次性嘲諷

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

所有僅基於Date.now()模擬的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.now()方法以將年份設置為 2010 以進行測試的方式

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

這對我有用:

const mockDate = new Date('14 Oct 1995')
global.Date = jest.fn().mockImplementation(() => mockDate) // mock Date "new" constructor
global.Date.now = jest.fn().mockReturnValue(mockDate.valueOf()) // mock Date.now

我想提供一些替代方法。

如果您需要存根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

我想使用手動模擬,所以它可以在所有測試中使用。

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

就我而言,我必須在測試前模擬整個 Date 和“now”函數:

const mockedData = new Date('2020-11-26T00:00:00.000Z');

jest.spyOn(global, 'Date').mockImplementation(() => mockedData);

Date.now = () => 1606348800;

describe('test', () => {...})

稍微改進@pranava-s-balugari 的響應

  1. 它不影響new Date(something)
  2. 可以更改模擬日期。
  3. 它也適用於 Date.now
const DateOriginal = global.Date;

global.Date = class extends DateOriginal {
    constructor(params) {
        if (params) {
          super(params)
        } else if (global.Date.NOW === undefined) {
          super()
        } else {
          super(global.Date.NOW)
        }
    }
    static now () {
      return new Date().getTime();
    }
}

afterEach(() => {
  global.Date.NOW = undefined;
})

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

describe('some test', () => {
  afterEach(() => NOW = undefined);

  it('some test', () => {
     Date.NOW = '1999-12-31T23:59:59' // or whatever parameter you could pass to new Date([param]) to get the date you want


     expect(new Date()).toEqual(new Date('1999-12-31T23:59:59'));
     expect(new Date('2000-01-01')).toEqual(new Date('2000-01-01'));
     expect(Date.now()).toBe(946681199000)

     Date.NOW = '2020-01-01'

     expect(new Date()).toEqual(new Date('2020-01-01'));
  })
})

目標是在組件渲染期間使用固定日期模擬 new Date() 以用於測試目的。 如果您只想模擬 new Date() fn,那么使用庫將是一種開銷。

想法是將全局日期存儲到臨時變量,模擬全局 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();
    });
});


我只是想在這里插一句,因為如果您只想在特定套件中模擬Date對象,則沒有答案解決該問題。

您可以使用每個套件的設置和拆卸方法來模擬它, jest docs

/**
 * 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;
});

希望這可以幫助!

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

我發現的最好方法就是用你正在使用的任何函數覆蓋原型。

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

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

以下測試存根 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()任何地方,它都會產生相同的時間戳。

注意:您可以將beforeEach替換為beforeAll _GLOBAL只是一個滿足打字稿的代理變量。

我試過的完整代碼:

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

它對我有用。

接受的答案效果很好 -

Date.now = jest.fn().mockReturnValue(new Date('2021-08-29T18:16:19+00:00'));

但是如果我們想在管道中運行單元測試,我們必須確保我們使用相同的時區。 為此,我們還必須模擬時區 -

開玩笑的配置文件

process.env.TZ = 'GMT';

module.exports = {
 ...
};

另請參閱:時區的完整列表(列 TZ 數據庫名稱)

我正在使用 moment + moment-timezone ,但這些都不適合我。

這有效:

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

我推薦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.now = jest.fn(() => new Date(Date.UTC(2021, 2, 30)).valueOf());

我在jest.config.jssetupFilesAfterEnv道具中的setupTests.ts文件集中寫了這個:

module.exports = {
    setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
};

要模擬toISOString你可以這樣做:

jest.spyOn(global.Date.prototype, 'toISOString').mockReturnValue('01-01-2001 00:00:00')

Jest版本29開始,您還可以執行以下操作:

jest.useFakeTimers({
  now: 1673445238335,
});

允許以下選項:

type FakeTimersConfig = {
  /**
   * If set to `true` all timers will be advanced automatically by 20 milliseconds
   * every 20 milliseconds. A custom time delta may be provided by passing a number.
   * The default is `false`.
   */
  advanceTimers?: boolean | number;
  /**
   * List of names of APIs that should not be faked. The default is `[]`, meaning
   * all APIs are faked.
   */
  doNotFake?: Array<FakeableAPI>;
  /**
   * Use the old fake timers implementation instead of one backed by `@sinonjs/fake-timers`.
   * The default is `false`.
   */
  legacyFakeTimers?: boolean;
  /** Sets current system time to be used by fake timers. The default is `Date.now()`. */
  now?: number | Date;
  /**
   * The maximum number of recursive timers that will be run when calling `jest.runAllTimers()`.
   * The default is `100_000` timers.
   */
  timerLimit?: number;
};

您可以在文檔中閱讀更多內容。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM