Fechas en Jest
Tags: javascript, typescript, testing, spanish
Published: 2022-02-18 16:05:35

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:

```js
beforeAll(() => {
  jest.useFakeTimers('modern')
  jest.setSystemTime(new Date(2022, 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`. 

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

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

```js
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):

```js
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`:

```js
spy.mockRestore()
```

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

```js
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](https://www.npmjs.com/package/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:

```ts
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:

```ts
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!