Mock fechas en Jest

Mock fechas en Jest

¿Sabes cómo mockear las fechas en Jest? Aprenderemos a cómo mockear las fechas en nuestros test.

Muchas veces me he encontrado ante la escritura de un test en el que, por la razón que sea, necesito mockear de alguna manera el resultado de la ejecución del método now del objeto Date y casi siempre he tenido que recurrir a Google para encontrar la solución por lo que me he dedicio a crear este breve artículo.

Jest >= 26

En primer lugar vamos a ver cómo podemos solucionar las cosas si estamos ante una de las últimas versiones de Jest (más concretamente si estamos trabajando con la versión 26 o superior). En este caso el objeto jest nos proporciona el método setSystemtime que hará precisamente lo que queremos: establecer el valor que queremos que devuelva el método now del objeto Date.

Pero la invocación de este método por si sola no es lo único que vamos a necesitar ya que además vamos a tener que indicarle a Jest que vamos a utilizar el método moderno (actual) para devover las fechas mockeadas cosa que haremos invocando al método useFakeTimers del objeto jest.

Si juntamos esto lo que podríamos hacer es que en nuestra suite de test antes de que se ejecute cualquiera de los test que precisamos correr se establezca el valor mockeado para now con algo como sigue:

beforeAll(() => {
  jest.useFakeTimers('modern')
  jest.setSystemTime(new Date(2024, 2, 1))
})

Si olvidarnos de que deberemos restaurar el objeto que generará las fechas en los test una vez finalice la ejecución de la suite. Esto lo lograremos invocando al método useRealTimers que nos proporciona también el objeto jest.

afterAll(() => {
  jest.useRealTimers()
})

En definitiva, que la estructura de nuestra suite de test debería ser algo como lo siguiente:

beforeAll(() => {
  jest.useFakeTimers('modern')
  jest.setSystemTime(new Date(2022, 2, 1))
})

afterAll(() => {
  jest.useRealTimers()
})

// Todos los test de nuestra suite.

Jest < 26

En las versiones anteriores a la 26 de Jest vamos a tener que hacer uso del método spyOn que nos proporciona Jest para poder crear una versión spy del método now entiendo que una versión de este tipo es una implementación de la misma con la salvedad de que va a retornar lo que nosotros queramos.

Es por ello que al irlo a utilizar dentro de nuestros test lo que escribiríamos sería algo como lo siguiente (primero lo mostramos y a continuación la explicación):

test('my test', () => {
  const mockDate = new Date(2022, 2, 1)
  const spy = jest
    .spyOn(global, 'Date'
    .mockImplementation(() => mockDate))

¿Qué es lo que estamos haciendo aquí? Pues en primer lugar crear el objeto que queremos que retorne cualquier llamada al objeto Date dentro de nuestro test asignándole la fecha que será la que se considerará mockeada. Hecho esto lo que tenemos que decirle a Jest es que vamos a crear un spy sobre el objeto Date y no solamente eso sino que gracias a la ejecución del método mockImplementation lo que le estaremos indicando es que cada vez que se invoque a la función Date (es decir, la función que permite crear un nuevo objeto) lo que queremos que retorne será siempre nuestro objeto mockeado.

A partir de aquí ya podemos seguir con nuestros test sin olvidarnos nunca de volver a restaurar la implementación del objeto Date gracias a la invocación del método mockRestore que nos ofrece el objeto spy que hemos obtenido como resultado de la invocación del método spyOn:

spy.mockRestore()

En resumen que la estructura de nuestro test quedaría algo así como:

test('my test', () => {
  const mockDate = new Date(2022, 2, 1)
  const spy = jest
    .spyOn(global, 'Date'
    .mockImplementation(() => mockDate))

  // realizar las operaciones del test....

  spy.mockRestore()
}

¿Qué pasa con TypeScript?

El problema con las aproximaciones que acabamos de ver es que desde el punto de vista de TypeScript nos den un error de tipos similar al siguiente:

Argument of type '() => Date' is not assignable to parameter of type '() => string'. Type 'Date' is not assignable to type 'string'

¿Qué podemos hacer en este caso? Aquí la solución pasa por hacer uso de una librería de terceros siendo la más utilizada mockdate. Pero ¿cómo la aplicamos una vez la instalemos? Lo primero que tenemos que saber es que esta librería nos va a proporcionar el objeto MockDate que contendrá toda al funcionalidad que necesitamos por lo que un primer paso siempre va a ser importarlo:

import MockDate from 'mockdate'

¿Y cómo podemos utilizarlo? Pues es realmente simple porque este objeto nos proporciona dos métodos para lograr nuestro objetivo endo el primero de ellos el método set que esperará recibir como parámetro un objeto Date con la fecha con la que queremos trabajar en nuestros test y el método reset que sirve para resetear la fecha del sistema. Esto nos deja con que la implementación de uno de nuestros test podría ser algo como lo siguiente:

import MockDate from 'mockdate'

it('my test', () => {
  MockDate.set(new Date(2022, 2, 1))

  // ... Operaciones de nuestro test ....

  MockDate.reset()
})

¡Mucho más sencillo y claro de entender!