Día 3. Testing nivel intermedio

Día 3. Testing nivel intermedio

Ya hemos visto cómo hacer tests unitarios y tests de integración. Ahora es momento de entrar en algunos conceptos más avanzados del testing. Por ejemplo, cómo testear código asíncrono o sacar el máximo partido a los hooks. También abordaremos tipos de test más delicados, como aquellos en los que necesitamos comprobar que una función se […]

Categorías: Cursos

Adrián Garzón Ximénez - Desarrollador Fullstack


Ya hemos visto cómo hacer tests unitarios y tests de integración. Ahora es momento de entrar en algunos conceptos más avanzados del testing. Por ejemplo, cómo testear código asíncrono o sacar el máximo partido a los hooks.

También abordaremos tipos de test más delicados, como aquellos en los que necesitamos comprobar que una función se haya ejecutado pero sin llegar a lanzarla. Por ejemplo, en los casos en que la ejecución de la función vaya a modificar nuestra base de datos o crear o eliminar archivos. A este tipo de efectos los llamamos “efectos secundarios” y, como comprenderás, debemos evitarlos a la hora de testear nuestro código.

Tests asíncronos

Lanzar una función asíncrona en un test no tendrá sentido si no esperamos a que se resuelva, ya que un test fallido no es más que un test() o it() que recibe un error. Por tanto, si utilizamos código asíncrono pero no esperamos a que se resuelva la promesa o el callback nuestro test terminará antes que la ejecución de nuestra función, jamás podrá fallar, y el test será inutil.

Pero, afortunadamente, podemos pasar funciones asíncronas a it o test para que espere a que se resuelvan antes de evaluar el expect().

Tests concurrentes

Las funciones test e it se pueden llamar como test.concurrent e it.concurrent (lo mismo ocurre con describe) para que todos los tests del archivo se ejecuten de forma concurrente. Ten en cuenta que esto no afectará a los tests de diferentes archivos, pues el comportamiento por defecto tanto de Jest como de Vitest es que, de hecho, se ejecuten concurrentemente.

Pero, ¿para qué queremos lanzar tests concurrentes? Básicamente porque la ejecución concurrente de los tests reduce su tiempo de ejecución: ya no tenemos que esperar a que termine un test para lanzar el siguiente, sino que podemos ejecutarlos todos a la vez.

Ten en cuenta que la ejecución de tests concurrentes puede conllevar problemas. Por ejemplo, cuando unos tests dependen de otros, ya que no tendremos garantizado su orden de ejecución como cuando los corremos en serie.

Efectos secundarios

En ocasiones deberemos testear funciones de las que se deriven efectos secundarios. Por ejemplo, llamadas al backend, creación de archivos, envio de emails o registro de datos en bases de datos. Para esto podemos utilizar mocks y espías:

  • Los mocks simulan una respuesta de la API o la ejecución de una función, para que podamos comprobar cómo funciona nuestra aplicación sin necesidad de realizar llamadas al backend.
  • Por su parte, los espías son envoltorios para funciones, que emulan haberlas lanzado para que podamos correr el test sin que la función real provoque sus efectos secundarios.

Mocks

Podemos reemplazar todas las funciones de un módulo con espías mediante el método mock:

vi.mock(‘fs’)

Ahora todas las funciones de fs han sido sustituidas por espías, por lo que podemos utilizar fs sin riesgo de escribir/editar o eliminar archivos. Además, en nuestro expect podemos utilizar assertions como toBeCalled, toBeCalledTimes o toHaveBeenCalledWith.

Espías

Basta con que importemos el objeto vi de vitest y creemos una función ficticia:

const spyFn = vi.fn();

A continuación podremos proporcionársela a nuestra función y el test runner comprobará si ha sido llamada:

expect(spyFn).toHaveBeenCalledWith(request);

Lógica personalizada

Al crear una función espía estamos creando una función vacía, que nos permite trackear si se ha llamado o no, cuántas veces, con cuantos argumentos… pero también podemos añadirle lógica personalizada para testear sus respuestas.

vi.mock("path", () => {

  return {

    default: {

      join: () => "mocked path",

    },

  };

});

El test runner buscará los mocks en la carpeta __mocks__, lo que nos permite centralizarlos.

Curso de introducción al testing

Visita mi repositorio intro-to-testing para ver ejemplos de todo lo que hemos visto a lo largo de este curso:

  1. ¿Qué es el testing?
  2. Unit testing
  3. Conceptos intermedios de testing
  4. Cómo hacer tests en el DOM