¿Cómo establezco una fecha simulada en Jest?

111

Estoy usando moment.js para hacer la mayor parte de mi lógica de fecha en un archivo auxiliar para mis componentes de React, pero no he podido averiguar cómo simular una cita en Jest a la sinon.useFakeTimers().

Los documentos de Jest solo hablan sobre funciones de temporizador como setTimeout, setIntervaletc. , pero no ayudan a establecer una fecha y luego verificar que mis funciones de fecha hagan lo que deben hacer.

Aquí hay algunos de mis archivos 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;

y esto es lo que configuré 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);
    });

  });

});

Ahora estas pruebas pasan porque estoy usando moment y mis funciones usan moment, pero parece un poco inestable y me gustaría establecer la fecha en un tiempo fijo para las pruebas.

¿Alguna idea de cómo se podría lograr?

ángel
fuente

Respuestas:

70

MockDate se puede usar en pruebas de broma para cambiar lo que new Date()devuelve:

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
fuente
Funcionó muy bien porque estaba usando otras funciones de Datelike valueOf().
Robin Zimmermann
143

Dado que momentjs se usa Dateinternamente, puede simplemente sobrescribir la Date.nowfunción para devolver siempre el mismo momento.

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

o

Date.now = jest.fn(() => new Date(Date.UTC(2017, 1, 14)).valueOf())
estereodenis
fuente
34
Aquí hay un método un poco más bonito para establecer la fecha real que se devolverá:Date.now = jest.fn(() => new Date(Date.UTC(2017, 0, 1)).valueOf());
desarrollo el
4
O incluso un poco más bonito:Date.now = jest.fn(() => +new Date('2017-01-01');
mrzmyr
3
O:Date.now = jest.fn(() => Date.parse('2017-02-14))
Jeremy Eaton
93

jest.spyOn funciona para bloquear el tiempo:

let dateNowSpy;

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

afterAll(() => {
    // Unlock Time
    dateNowSpy.mockRestore();
});
Tim Santeford
fuente
3
Gran solucion; sin dependencias y mantenerlo reiniciable hace que sea fácil de aplicar a una sola prueba.
Caleb Miller
14
No se necesitan dateNowSpyvariables, y mockReset()es redundante de acuerdo con jestjs.io/docs/en/mock-function-api.html#mockfnmockrestore . En el afterAll, simplemente puede hacerloDate.now.mockRestore()
Jimmy
esto es genial, entonces no necesitas bibliotecas adicionales. Pero esto solo funcionará realmente si está utilizando métodos de fecha estáticos (que no son muchos)
hellatan
1
@Jimmy Date.now.mockRestore();da una 'mockRestore' propiedad no existe en el tipo '() => número de' error
Marco Lacković
3
@Marco debería ser jest.spyOn (Fecha, "ahora"). MockRestore ();
sab
6

jest-date-mock es un módulo completo de JavaScript escrito por mí, y se usa para probar Date en 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
});

Utilice las únicas 3 api para casos de prueba.

  • advanceBy (ms): fecha y hora de avance en ms.
  • advanceTo ([timestamp]): restablece la fecha a la marca de tiempo, el valor predeterminado es 0.
  • clear (): apaga el sistema simulado.
una herramienta
fuente
cual es tu caso
atool
5

Para aquellos que quieran simular métodos en un nuevo objeto Date, pueden hacer lo siguiente:

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

afterEach(() => {
    jest.restoreAll()
});
RobotEyes
fuente
Gracias, esto acaba de solucionar el problema que estaba teniendo.
Grayson Langford
2

Toda la respuesta basada solo en la simulación de Date.now()no funcionará en todas partes ya que algunos paquetes (por ejemplo moment.js) usannew Date() en lugar.

En este contexto, la respuesta basada en MockDatecreo que es la única verdaderamente correcta. Si no desea utilizar un paquete externo, puede escribir directamente en su 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;
ClementeWalter
fuente
2

Me gustaría ofrecer algunos enfoques alternativos.

Si necesita stub format()(que puede depender de la ubicación y la zona horaria)

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

Si solo necesita talón moment():

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

Con respecto a la prueba para la isDateTodayfunción anterior, creo que la forma más sencilla sería no burlarse momenten absoluto

David
fuente
2
Para el primer ejemplo, obtengoTypeError: moment.mockReturnValue is not a function
mkelley33
2
¿Está jest.mock("moment")al mismo nivel que sus declaraciones de importación? De lo contrario, puede verlo en acción en este proyecto
David
1

Así es como me burlé de mi Date.now()método para establecer el año en 2010 para mi prueba

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

A continuación, se muestran algunas formas legibles para diferentes casos de uso. Prefiero usar espías en lugar de guardar referencias a los objetos originales, que pueden sobrescribirse accidentalmente en algún otro código.

Burla única

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

Algunas pruebas

let dateSpy;

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

afterAll(() => {
  dateSpy.mockRestore();
});
Yangshun Tay
fuente
0

Me gustaría usar Manual Mocks, para que pueda usarse en todas las pruebas.

// <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
código elegante
fuente
0

El objetivo es simular el nuevo Date () con una fecha fija donde sea que se use durante la representación del componente con fines de prueba. El uso de bibliotecas será una sobrecarga si lo único que desea es simular el nuevo Date () fn.

La idea es almacenar la fecha global en una variable temporal, simular el dae global y luego, después del uso, reasignar la temperatura a la fecha global.

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
fuente
Explique también su respuesta. poner solo código no es el buen enfoque
Intsab Haider
1
Gracias por la sugerencia. Actualizado con comentarios.
Pranava S Balugari
0

Solo quería intervenir aquí ya que ninguna respuesta abordó el problema si desea burlarse del Dateobjeto solo en una suite específica.

Puede simularlo usando los métodos de configuración y desmontaje para cada 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;
});

¡Espero que esto ayude!

MoMo
fuente
0

Puedes usar date-faker . Le permite cambiar la fecha actual 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();
MatGar
fuente