Comment définir une date fictive dans Jest?

112

J'utilise moment.js pour faire la plupart de ma logique de date dans un fichier d'aide pour mes composants React mais je n'ai pas été en mesure de comprendre comment simuler une date dans Jest a la sinon.useFakeTimers().

Les documents Jest ne parlent que des fonctions de minuterie telles que setTimeout, setIntervaletc., mais n'aident pas à définir une date et à vérifier ensuite que mes fonctions de date font ce qu'elles sont censées faire.

Voici quelques-uns de mes fichiers 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;

et voici ce que j'ai mis en place en utilisant 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);
    });

  });

});

Maintenant, ces tests réussissent car j'utilise moment et mes fonctions utilisent moment mais cela semble un peu instable et j'aimerais fixer la date à une heure fixe pour les tests.

Une idée sur la façon dont cela pourrait être accompli?

Alengel
la source

Réponses:

70

MockDate peut être utilisé dans les tests de plaisanterie pour changer ce qui new Date()retourne:

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();
eadmundo
la source
A très bien fonctionné car j'utilisais d'autres fonctions Datesimilaires valueOf().
Robin Zimmermann
145

Puisque momentjs utilise en Dateinterne, vous pouvez simplement écraser la Date.nowfonction pour toujours renvoyer le même moment.

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

ou

Date.now = jest.fn(() => new Date(Date.UTC(2017, 1, 14)).valueOf())
stéréodénis
la source
34
Voici une méthode un peu plus jolie pour définir la date réelle qui sera renvoyée:Date.now = jest.fn(() => new Date(Date.UTC(2017, 0, 1)).valueOf());
développement du
5
Ou même un peu plus joli:Date.now = jest.fn(() => +new Date('2017-01-01');
mrzmyr
3
OU:Date.now = jest.fn(() => Date.parse('2017-02-14))
Jeremy Eaton le
93

jest.spyOn fonctionne pour le temps de verrouillage:

let dateNowSpy;

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

afterAll(() => {
    // Unlock Time
    dateNowSpy.mockRestore();
});
Tim Santeford
la source
3
Excellente solution; aucune dépendance et le garder réinitialisable le rend facile à appliquer à un seul test.
Caleb Miller
14
Pas besoin de dateNowSpyvariable, et le mockReset()est redondant selon jestjs.io/docs/en/mock-function-api.html#mockfnmockrestore . Dans le afterAll, vous pouvez simplement faireDate.now.mockRestore()
Jimmy
c'est génial donc vous n'avez pas besoin de bibliothèques supplémentaires. Mais cela ne fonctionnera vraiment que si vous utilisez des méthodes de date statiques (qui ne sont pas nombreuses)
hellatan
1
@Jimmy Date.now.mockRestore();donne une propriété 'mockRestore' n'existe pas sur le type '() => number' error
Marco Lackovic
3
@Marco cela devrait être jest.spyOn (Date, "now"). MockRestore ();
sab
6

jest-date-mock est un module javascript complet écrit par moi, et il est utilisé pour tester Date sur 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
});

Utilisez la seule API 3 pour les cas de test.

  • advanceBy (ms): horodatage de la date d'avance de ms.
  • advanceTo ([timestamp]): réinitialise la date à l'horodatage, par défaut à 0.
  • clear (): arrête le système simulé.
un outil
la source
quel est votre cas?
atool le
5

Pour ceux qui souhaitent simuler des méthodes sur un nouvel objet Date, vous pouvez effectuer les opérations suivantes:

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

afterEach(() => {
    jest.restoreAll()
});
RobotEyes
la source
Merci, cela vient de résoudre le problème que j'avais.
Grayson Langford
2

Toutes les réponses basées uniquement sur le simulacre de Date.now()ne fonctionneront pas partout puisque certains packages (par exemple moment.js) l'utilisent à la new Date()place.

Dans ce contexte, la réponse basée sur MockDateest, je pense, la seule vraiment correcte. Si vous ne souhaitez pas utiliser de package externe, vous pouvez écrire directement dans votre 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;
ClémentWalter
la source
2

Je voudrais proposer des approches alternatives.

Si vous avez besoin de stub format()(qui peut dépendre de la locale et du fuseau horaire!)

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

Si vous avez seulement besoin de stub moment():

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

En ce qui concerne le test de la isDateTodayfonction ci-dessus, je pense que le moyen le plus simple serait de ne pas se moquer momentdu tout

David
la source
2
Pour le premier exemple, je reçoisTypeError: moment.mockReturnValue is not a function
mkelley33
2
Est-ce jest.mock("moment")au même niveau que vos déclarations d'importation? Sinon, vous êtes invités à le voir en action dans ce projet
David
1

C'est ainsi que je me suis moqué de ma Date.now()méthode pour fixer l'année 2010 à mon test

jest
  .spyOn(global.Date, 'now')
  .mockImplementationOnce(() => new Date(`2010`).valueOf());
Dawood Valeed
la source
1

Voici quelques méthodes lisibles pour différents cas d'utilisation. Je préfère utiliser des espions plutôt que d'enregistrer des références aux objets d'origine, qui peuvent être accidentellement écrasés dans un autre code.

Moquerie ponctuelle

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

Quelques tests

let dateSpy;

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

afterAll(() => {
  dateSpy.mockRestore();
});
Yangshun Tay
la source
1

Depuis Jest 26, cela peut être réalisé en utilisant de faux minuteries "modernes" sans avoir besoin d'installer de modules tiers: https://jestjs.io/blog/2020/05/05/jest-26#new-fake-timers

jest
  .useFakeTimers('modern')
  .setSystemTime(new Date('2020-01-01').getTime());
SimenB
la source
merci l'homme, je pense que cela devrait être la solution de cette question.
Shahzad Mirza il y a
0

J'aimerais utiliser Manual Mocks, afin qu'il puisse être utilisé dans tous les tests.

// <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
codelegant
la source
0

L'objectif est de simuler la nouvelle Date () avec une date fixe partout où elle est utilisée lors du rendu du composant à des fins de test. L'utilisation de bibliothèques sera une surcharge si la seule chose que vous voulez est de vous moquer de new Date () fn.

L'idée est de stocker la date globale dans une variable temporaire, de simuler le dae global, puis après l'utilisation, de réaffecter la température à la date 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();
    });
});


Pranava S Balugari
la source
Expliquez également votre réponse. mettre uniquement du code n'est pas la bonne approche
Intsab Haider
1
Merci pour la suggestion. Mis à jour avec des commentaires.
Pranava S Balugari
0

Je voulais juste intervenir ici car aucune réponse ne résout le problème si vous souhaitez vous moquer de l' Dateobjet dans une suite spécifique uniquement.

Vous pouvez vous en moquer en utilisant les méthodes de configuration et de démontage pour chaque 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;
});

J'espère que cela t'aides!

MoMo
la source
0

Vous pouvez utiliser date-faker . Permet de modifier la date actuelle de manière relative:

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();
MatGar
la source