Jak ustawić pozorowaną datę w Jest?

112

Używam momentu.js, aby wykonać większość logiki dat w pliku pomocniczym dla moich komponentów React, ale nie byłem w stanie wymyślić, jak zakpić datę w Jest a la sinon.useFakeTimers().

W żartach docs tylko mówić o funkcjach czasowych, takich jak setTimeout, setIntervaletc, ale nie pomagają z ustawieniem daty i sprawdzeniu, że moim dniu funkcje zrobić co oni oznaczało zrobić.

Oto część mojego pliku 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;

a oto, co skonfigurowałem za pomocą 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);
    });

  });

});

Teraz te testy przechodzą, ponieważ używam momentu, a moje funkcje używają momentu, ale wydaje się to trochę niestabilne i chciałbym ustawić datę na stały czas dla testów.

Masz jakiś pomysł, jak można to osiągnąć?

alengel
źródło

Odpowiedzi:

70

MockDate może być używany w testach żart, aby zmienić to, co new Date()zwraca:

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
źródło
Działało świetnie, ponieważ korzystałem z innych funkcji, Datetakich jak valueOf().
Robin Zimmermann
145

Ponieważ momentjs używa Datewewnętrznie, możesz po prostu nadpisać Date.nowfunkcję, aby zawsze zwracała ten sam moment.

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

lub

Date.now = jest.fn(() => new Date(Date.UTC(2017, 1, 14)).valueOf())
stereodenis
źródło
34
Oto trochę ładniejsza metoda ustawiania faktycznej daty, która zostanie zwrócona:Date.now = jest.fn(() => new Date(Date.UTC(2017, 0, 1)).valueOf());
wywołanie
5
Albo nawet trochę ładniej:Date.now = jest.fn(() => +new Date('2017-01-01');
mrzmyr
3
LUB:Date.now = jest.fn(() => Date.parse('2017-02-14))
Jeremy Eaton
93

jest.spyOn działa na czas blokowania:

let dateNowSpy;

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

afterAll(() => {
    // Unlock Time
    dateNowSpy.mockRestore();
});
Tim Santeford
źródło
3
Świetne rozwiązanie; brak zależności i zachowanie możliwości resetowania ułatwia zastosowanie do pojedynczego testu.
Caleb Miller,
14
Nie ma potrzeby używania dateNowSpyzmiennej, a mockReset()zgodnie z jestjs.io/docs/en/mock-function-api.html#mockfnmockrestore jest on zbędny . W afterAll, możesz po prostu zrobićDate.now.mockRestore()
Jimmy
to jest świetne, więc nie potrzebujesz żadnych dodatkowych bibliotek. Ale to naprawdę zadziała tylko wtedy, gdy używasz statycznych metod Date (których nie jest wiele)
hellatan
1
@Jimmy Date.now.mockRestore();daje właściwość `` mockRestore '' nie istnieje w przypadku błędu typu '() => number'
Marco Lackovic
3
@Marco powinno to być jest.spyOn (Date, "now"). MockRestore ();
sab
6

jest-date-mock to napisany przeze mnie kompletny moduł javascript, który służy do testowania 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
});

Użyj tylko 3 interfejsów API dla przypadków testowych.

  • AdvanceBy (ms): sygnatura czasowa z wyprzedzeniem według ms.
  • AdvanceTo ([timestamp]): resetowanie daty do znacznika czasu, domyślnie 0.
  • clear (): zamknij fałszywy system.
narzędzie
źródło
jaki jest twój przypadek
atool
5

Dla tych, którzy chcą mockować metody w nowym obiekcie Date, możesz wykonać następujące czynności:

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

afterEach(() => {
    jest.restoreAll()
});
RobotEyes
źródło
Dzięki, to właśnie rozwiązało problem, który miałem.
Grayson Langford
2

Cała odpowiedź oparta tylko na makiecie Date.now()nie będzie działać wszędzie, ponieważ niektóre pakiety (na przykład moment.js) używają new Date()zamiast tego.

W tym kontekście odpowiedź, na której oparto MockDate, wydaje mi się jedyna prawdziwie poprawna. Jeśli nie chcesz korzystać z zewnętrznego pakietu, możesz napisać bezpośrednio w swoim 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;
ClementWalter
źródło
2

Chciałbym zaproponować alternatywne podejścia.

Jeśli potrzebujesz odgiąć format()(co może zależeć od lokalizacji i strefy czasowej!)

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

Jeśli potrzebujesz tylko odgiąć moment():

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

Jeśli chodzi o test dla isDateTodayfunkcji powyżej, uważam, że najprostszym sposobem nie byłoby do makiety momentw ogóle

David
źródło
2
Jako pierwszy przykład otrzymujęTypeError: moment.mockReturnValue is not a function
mkelley33
2
Czy jest jest.mock("moment")na tym samym poziomie co instrukcje importu? W przeciwnym razie możesz zobaczyć to w akcji w tym projekcie
David
1

W ten sposób kpiłem z mojej Date.now()metody ustawiania roku na rok 2010 dla mojego testu

jest
  .spyOn(global.Date, 'now')
  .mockImplementationOnce(() => new Date(`2010`).valueOf());
Dawood Valeed
źródło
1

Oto kilka czytelnych sposobów dla różnych przypadków użycia. Wolę używać szpiegów niż zapisywać odniesienia do oryginalnych obiektów, które mogą zostać przypadkowo nadpisane w innym kodzie.

Jednorazowe kpiny

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

Kilka testów

let dateSpy;

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

afterAll(() => {
  dateSpy.mockRestore();
});
Yangshun Tay
źródło
1

Od Jest 26 można to osiągnąć za pomocą „nowoczesnych” fałszywych timerów bez konieczności instalowania jakichkolwiek modułów innych firm: https://jestjs.io/blog/2020/05/05/jest-26#new-fake-timers

jest
  .useFakeTimers('modern')
  .setSystemTime(new Date('2020-01-01').getTime());
SimenB
źródło
dzięki stary myślę, że to powinno być rozwiązanie tego pytania.
Shahzad Mirza
0

Chciałbym użyć ręcznych makiet, aby można go było używać we wszystkich testach.

// <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
kodelegant
źródło
0

Celem jest mockowanie nowej Date () ze stałą datą, gdziekolwiek jest ona używana podczas renderowania komponentu w celach testowych. Korzystanie z bibliotek będzie narzutem, jeśli jedyną rzeczą, którą chcesz, jest mockowanie nowej Date () fn.

Pomysł polega na przechowywaniu globalnej daty w zmiennej tymczasowej, mockowaniu globalnego dae, a następnie po użyciu ponownie przypisanie temp do daty globalnej.

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
źródło
Wyjaśnij również swoją odpowiedź. wprowadzenie samego kodu nie jest dobrym podejściem
Intsab Haider
1
Dzieki za sugestie. Zaktualizowany komentarzami.
Pranava S Balugari
0

Chciałem się tutaj tylko zabezpieczyć, ponieważ żadna odpowiedź nie rozwiązała problemu, jeśli chcesz kpić z Dateobiektu tylko w określonym pakiecie.

Możesz go kpić, używając metod konfiguracji i usuwania dla każdego pakietu, 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;
});

Mam nadzieję że to pomoże!

MoMo
źródło
0

Możesz użyć fałszerza daty . Pozwala względnie zmienić aktualną datę:

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
źródło