Como faço para definir uma data simulada no Jest?


112

Estou usando o moment.js para fazer a maior parte da minha lógica de data em um arquivo auxiliar para meus componentes React, mas não consegui descobrir como simular um encontro em Jest a la sinon.useFakeTimers() .

Os documentos do Jest falam apenas sobre funções de cronômetro como setTimeout,setInterval etc. mas não ajudam a definir uma data e, em seguida, verificar se minhas funções de data fazem o que devem fazer.

Aqui está um pouco do meu arquivo 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;

e aqui está o que configurei usando o 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);
    });

  });

});

Agora esses testes passam porque estou usando momento e minhas funções usam momento, mas parece um pouco instável e gostaria de definir a data para um horário fixo para os testes.

Alguma ideia de como isso pode ser feito?

Respostas:


70

MockDate pode ser usado em testes de brincadeira para alterar o que new Date()retorna:

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

Funcionou muito bem porque estava usando outras funções de Datelike valueOf().
Robin Zimmermann

144

Como o momentjs usa Dateinternamente, você pode simplesmente substituir a Date.nowfunção para sempre retornar o mesmo momento.

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

ou

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

34
Aqui está um método um pouco mais bonito de definir a data real que será retornada:Date.now = jest.fn(() => new Date(Date.UTC(2017, 0, 1)).valueOf());
desenvolvendo

4
Ou até um pouco mais bonito:Date.now = jest.fn(() => +new Date('2017-01-01');
mrzmyr

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

93

jest.spyOn funciona para bloquear o tempo:

let dateNowSpy;

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

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

3
Ótima solução; sem dependências e mantê-lo redefinível facilita a aplicação a um único teste.
Caleb Miller de

14
Não há necessidade de dateNowSpyvariável e mockReset()é redundante de acordo com jestjs.io/docs/en/mock-function-api.html#mockfnmockrestore . No afterAll, você pode simplesmente fazerDate.now.mockRestore()
Jimmy

isso é ótimo, então você não precisa de nenhuma biblioteca adicional. Mas isso só funcionará realmente se você estiver usando métodos de data estáticos (que não são muitos)
hellatan

1
@Jimmy Date.now.mockRestore();fornece uma propriedade 'mockRestore' não existe no erro de tipo '() => número'
Marco Lackovic

3
@Marco deve ser jest.spyOn (Date, "agora"). MockRestore ();
sab

6

jest-date-mock é um módulo javascript completo escrito por mim, e é usado para testar Date em 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
});

Use apenas 3 api para casos de teste.

  • advanceBy (ms): data e hora de avanço em ms.
  • advanceTo ([timestamp]): redefine a data para o timestamp, o padrão é 0.
  • clear (): fecha o sistema simulado.

qual é o seu caso?
atool

5

Para aqueles que desejam simular métodos em um novo objeto Date, você pode fazer o seguinte:

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

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

Obrigado, isso apenas corrigiu o problema que eu estava tendo.
Grayson Langford

2

Todas as respostas baseadas apenas no mock de Date.now()não funcionarão em todos os lugares, pois alguns pacotes (por exemplo moment.js) usamnew Date() .

Neste contexto, a resposta baseada em MockDateacho que é a única verdadeiramente correta. Se não quiser usar um pacote externo, você pode escrever diretamente em 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

Eu gostaria de oferecer algumas abordagens alternativas.

Se você precisar fazer um stub format()(que pode depender da localidade e do fuso horário!)

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

Se você só precisa de stub moment():

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

Em relação ao teste para a isDateTodayfunção acima, acredito que a maneira mais simples seria não zombar de momentnada


2
Para o primeiro exemplo, receboTypeError: moment.mockReturnValue is not a function
mkelley33

2
Está jest.mock("moment")no mesmo nível que suas declarações de importação? Caso contrário, você é bem-vindo para vê-lo em ação neste projeto
David

1

É assim que eu zombei do meu Date.now()método para definir o ano de 2010 para o meu teste

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

1

Aqui estão algumas maneiras legíveis para diferentes casos de uso. Eu prefiro usar espiões em vez de salvar referências aos objetos originais, que podem ser substituídos acidentalmente em algum outro código.

Zombaria única

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

Alguns testes

let dateSpy;

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

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


0

Eu gostaria de usar Mocks manuais, para que possa ser usado em todos os testes.

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

O objetivo é simular new Date () com uma data fixa onde quer que seja usado durante a renderização do componente para fins de teste. Usar bibliotecas será uma sobrecarga se a única coisa que você quiser é simular new Date () fn.

A ideia é armazenar a data global em uma variável temporária, simular o dae global e, depois do uso, reatribuir temp para a data 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();
    });
});



Explique sua resposta também. colocar apenas o código não é a melhor abordagem
Intsab Haider

1
Obrigado pela sugestão. Atualizado com comentários.
Pranava S Balugari de

0

Eu só queria gritar aqui, já que nenhuma resposta abordou o problema se você quiser zombar do Date objeto em apenas um conjunto específico.

Você pode simular usando os métodos de configuração e desmontagem para cada suíte, 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 isto ajude!


0

Você pode usar data-faker . Permite que você altere a data atual 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();
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.