ฉันจะกำหนดวันที่จำลองใน Jest ได้อย่างไร?


120

ฉันใช้ moment.js เพื่อใช้ตรรกะวันที่ส่วนใหญ่ในไฟล์ตัวช่วยสำหรับส่วนประกอบ React ของฉัน แต่ฉันไม่สามารถหาวิธีล้อเลียนวันที่ใน Jest a la sinon.useFakeTimers()ได้

เอกสารล้อเล่นเท่านั้นพูดเกี่ยวกับฟังก์ชั่นจับเวลาเหมือน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);
    });

  });

});

ตอนนี้การทดสอบเหล่านี้ผ่านไปเพราะฉันใช้โมเมนต์และฟังก์ชันของฉันใช้โมเมนต์ แต่ดูเหมือนจะไม่เสถียรเล็กน้อยและฉันต้องการกำหนดวันที่เป็นเวลาที่แน่นอนสำหรับการทดสอบ

มีความคิดว่าจะทำได้อย่างไร?

คำตอบ:


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

การทำงานที่ดีเพราะผมใช้ฟังก์ชั่นอื่น ๆ ของชอบDate valueOf()
Robin Zimmermann

150

เนื่องจาก 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());
พัฒนา

5
หรือสวยกว่านี้หน่อย:Date.now = jest.fn(() => +new Date('2017-01-01');
mrzmyr

3
หรือ:Date.now = jest.fn(() => Date.parse('2017-02-14))
Jeremy Eaton

1
สิ่งนี้จะล้อเลียนการใช้งานทั้งหมดDateหรือไม่? ชอบnew Date()?
Elias Zamaria

95

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()
จิมมี่

มันยอดเยี่ยมมากดังนั้นคุณไม่จำเป็นต้องมีไลบรารีเพิ่มเติม แต่จะใช้งานได้จริงก็ต่อเมื่อคุณใช้วิธีวันที่แบบคงที่ (ซึ่งมีไม่มากนัก)
hellatan

1
@Jimmy Date.now.mockRestore();ให้Property 'mockRestore' ไม่มีอยู่ในประเภท '() => number' error
Marco Lackovic

3
@Marco มันควรจะเป็น jest.spyOn (วันที่, "now"). mockRestore ();
แซบ

6

jest-date-mockเป็นโมดูลจาวาสคริปต์ที่สมบูรณ์ซึ่งเขียนโดยฉันและใช้เพื่อทดสอบ Date on jest

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): การประทับวันที่ล่วงหน้าโดยมิลลิวินาที
  • advanceTo ([timestamp]): รีเซ็ตวันที่เป็นการประทับเวลาโดยค่าเริ่มต้นคือ 0
  • clear (): ปิดระบบจำลอง

กรณีของคุณคืออะไร?
atool

6

สำหรับผู้ที่ต้องการจำลองวิธีการบนวัตถุ Date ใหม่คุณสามารถทำได้ดังต่อไปนี้:

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

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

ขอบคุณนี่ช่วยแก้ปัญหาที่ฉันพบได้
Grayson Langford

6

ใน Jest 26 สามารถทำได้โดยใช้ตัวจับเวลาปลอม "สมัยใหม่" โดยไม่จำเป็นต้องติดตั้งโมดูลของบุคคลที่สาม: https://jestjs.io/blog/2020/05/05/jest-26#new-fake-timers

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

ขอบคุณมากฉันคิดว่านี่ควรเป็นทางออกของคำถามนี้
Shahzad

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

2
อยู่jest.mock("moment")ในระดับเดียวกับใบแจ้งยอดการนำเข้าของคุณหรือไม่ มิฉะนั้นคุณสามารถเห็นการดำเนินการในโครงการนี้ได้
David

2

ต่อไปนี้เป็นวิธีที่สามารถอ่านได้สำหรับกรณีการใช้งานต่างๆ ฉันชอบใช้การสอดแนมมากกว่าการบันทึกการอ้างอิงไปยังวัตถุดั้งเดิมซึ่งอาจถูกเขียนทับโดยไม่ได้ตั้งใจในโค้ดอื่น ๆ

ล้อเลียนครั้งเดียว

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

1

นี่คือวิธีที่ฉันล้อเลียนDate.now()วิธีการตั้งค่าปีเป็น 2010 สำหรับการทดสอบของฉัน

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

0

ฉันต้องการใช้ Manual Mocks เพื่อให้สามารถใช้ในการทดสอบทั้งหมดได้

// <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 () ใหม่โดยมีวันที่ที่แน่นอนทุกที่ที่ใช้ในระหว่างการแสดงผลส่วนประกอบเพื่อวัตถุประสงค์ในการทดสอบ การใช้ไลบรารีจะเป็นค่าใช้จ่ายหากสิ่งเดียวที่คุณต้องการคือการล้อเลียน Date () fn ใหม่

แนวคิดคือการจัดเก็บวันที่ทั่วโลกเป็นตัวแปร temp ล้อเลียน dae ทั่วโลกและหลังจากการใช้งานกำหนดอุณหภูมิใหม่เป็นวันที่ส่วนกลาง

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วัตถุในชุดเฉพาะ

คุณสามารถล้อเลียนได้โดยใช้วิธีการตั้งค่าและการฉีกขาดสำหรับแต่ละชุดเอกสาร jest

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

คุณสามารถใช้วันที่กุขึ้น ให้คุณเปลี่ยนวันที่ปัจจุบันค่อนข้าง:

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();
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.