Come faccio a impostare una data fittizia in Jest?


112

Sto usando moment.js per fare la maggior parte della mia logica di data in un file di supporto per i miei componenti React ma non sono stato in grado di capire come deridere un appuntamento in Jest a la sinon.useFakeTimers().

I documenti di Jest parlano solo di funzioni timer come setTimeout, setIntervalecc. , Ma non aiutano a impostare una data e quindi a controllare che le mie funzioni di data facciano quello che dovrebbero fare.

Ecco alcuni dei miei file 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;

ed ecco cosa ho impostato usando 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);
    });

  });

});

Ora questi test passano perché sto usando moment e le mie funzioni usano moment ma sembra un po 'instabile e vorrei impostare la data su un tempo fisso per i test.

Qualche idea su come ciò potrebbe essere realizzato?

Risposte:


70

MockDate può essere utilizzato nei test jest per modificare ciò che new Date()restituisce:

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

Ha funzionato alla grande perché stavo usando altre funzioni Datesimili valueOf().
Robin Zimmermann

144

Poiché momentjs utilizza Dateinternamente, puoi semplicemente sovrascrivere la Date.nowfunzione per restituire sempre lo stesso momento.

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

o

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

34
Ecco un metodo un po 'più carino per impostare la data effettiva che verrà restituita:Date.now = jest.fn(() => new Date(Date.UTC(2017, 0, 1)).valueOf());
sviluppo

4
O anche un po 'più carina:Date.now = jest.fn(() => +new Date('2017-01-01');
mrzmyr

3
OPPURE:Date.now = jest.fn(() => Date.parse('2017-02-14))
Jeremy Eaton l'

93

jest.spyOn funziona per il tempo di blocco:

let dateNowSpy;

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

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

3
Ottima soluzione; nessuna dipendenza e mantenerlo ripristinabile semplifica l'applicazione a un singolo test.
Caleb Miller

14
Non c'è bisogno di dateNowSpyvariabile, ed mockReset()è ridondante secondo jestjs.io/docs/en/mock-function-api.html#mockfnmockrestore . In afterAll, puoi semplicemente fareDate.now.mockRestore()
Jimmy

questo è fantastico, quindi non hai bisogno di librerie aggiuntive. Ma questo funzionerà davvero solo se stai usando metodi di data statici (che non sono molti)
hellatan

1
@Jimmy Date.now.mockRestore();fornisce una proprietà 'mockRestore' non esiste sul tipo '() => numero' errore
Marco Lackovic

3
@ Marco dovrebbe essere jest.spyOn (Date, "now"). MockRestore ();
sab

6

jest-date-mock è un modulo javascript completo scritto da me, ed è usato per testare Date su 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
});

Usa le sole 3 API per i casi di test.

  • advanceBy (ms): data e ora in anticipo di ms.
  • advanceTo ([timestamp]): reimposta la data su timestamp, il valore predefinito è 0.
  • clear (): chiude il sistema fittizio.

qual è il tuo caso?
atool

5

Per coloro che vogliono simulare metodi su un nuovo oggetto Date, puoi fare quanto segue:

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

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

Grazie, questo ha appena risolto il problema che stavo avendo.
Grayson Langford

2

Tutta la risposta basata solo sulla simulazione di Date.now()non funzionerà ovunque poiché alcuni pacchetti (ad esempio moment.js) usanonew Date() invece.

In questo contesto la risposta su cui basarsi MockDatecredo sia l'unica veramente corretta. Se non vuoi usare un pacchetto esterno, puoi scrivere direttamente nel tuo 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

Vorrei offrire alcuni approcci alternativi.

Se è necessario eseguire lo stub format()(che può dipendere dalla località e dal fuso orario!)

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

Se hai solo bisogno di stub moment():

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

Per quanto riguarda il test per la isDateTodayfunzione sopra, credo che il modo più semplice sarebbe non deridere momentaffatto


2
Per il primo esempio, ottengoTypeError: moment.mockReturnValue is not a function
mkelley33

2
È jest.mock("moment")allo stesso livello delle tue dichiarazioni di importazione? Altrimenti, puoi vederlo in azione in questo progetto
David

1

È così che ho deriso il mio Date.now()metodo per impostare l'anno al 2010 per il mio test

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

1

Ecco alcuni modi leggibili per diversi casi d'uso. Preferisco usare le spie piuttosto che salvare i riferimenti agli oggetti originali, che possono essere sovrascritti accidentalmente in qualche altro codice.

Una derisione una tantum

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

Alcuni test

let dateSpy;

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

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


0

Vorrei usare Mock manuali, in modo che possa essere utilizzato in tutti i test.

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

L'obiettivo è simulare il nuovo Date () con una data fissa ovunque venga utilizzato durante il rendering del componente a scopo di test. Usare le librerie sarà un sovraccarico se l'unica cosa che vuoi è deridere new Date () fn.

L'idea è di memorizzare la data globale in una variabile temporanea, simulare il dae globale e quindi, dopo l'uso, riassegnare la data alla data globale.

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



Spiega anche la tua risposta. mettere solo il codice non è il buon approccio
Intsab Haider

1
Grazie per il suggerimento. Aggiornato con commenti.
Pranava S Balugari

0

Volevo solo intervenire qui poiché nessuna risposta ha affrontato il problema se si desidera deridere l' Dateoggetto solo in una suite specifica.

Puoi deriderlo usando i metodi di installazione e smontaggio per ogni suite, 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;
});

Spero che questo ti aiuti!


0

Puoi usare il datario . Consente di modificare la data corrente relativamente:

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();
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.