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?

Alengel
fonte

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();
eadmundo
fonte
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())
estereodeno
fonte
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();
});
Tim Santeford
fonte
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.
uma ferramenta
fonte
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()
});
RobotEyes
fonte
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;
ClementWalter
fonte
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

David
fonte
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());
Dawood Valeed
fonte
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();
});
Yangshun Tay
fonte
1

A partir de Jest 26, isso pode ser alcançado usando temporizadores falsos "modernos" sem a necessidade de instalar quaisquer módulos de terceiros: https://jestjs.io/blog/2020/05/05/jest-26#new-fake-timers

jest
  .useFakeTimers('modern')
  .setSystemTime(new Date('2020-01-01').getTime());
SimenB
fonte
obrigado cara eu acho que essa deveria ser a solução dessa questão.
Shahzad Mirza
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
co-elegante
fonte
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();
    });
});


Pranava S Balugari
fonte
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!

MoMo
fonte
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();
MatGar
fonte