Snapshot Testing con Jest

Snapshot Testing con Jest

Snapshot Testing con Jest

El snapshot testing es una técnica de pruebas de software que consiste en tomar una instantánea del estado actual de una aplicación y compararla con una instantánea anterior. Si las dos instantáneas no coinciden, el test falla.

En el caso de Jest, el snapshot testing se utiliza principalmente para probar componentes de interfaz de usuario (UI). La idea es tomar una instantánea del componente renderizado y compararla con una instantánea anterior. Si el componente se ha actualizado, la nueva instantánea se guardará y el test pasará. Si el componente no se ha actualizado, el test fallará.

El Snapshot Testing en Jest no crea imágenes visuales o representaciones gráficas de la interfaz de usuario de la aplicación. En cambio, genera instantáneas de cómo se ve la salida (generalmente representada como texto) de componentes de la interfaz de usuario o funciones en un momento dado.

Las "instantáneas" en el contexto del Snapshot Testing son representaciones serializadas de datos, que pueden ser texto o estructuras de datos en formato JSON. Estas instantáneas se almacenan en archivos junto con las pruebas. Cuando se ejecuta una prueba, Jest compara la salida actual de tu código con la instantánea previamente almacenada.

¿Cómo se configura?

Para configurar el snapshot testing con Jest, primero necesitamos instalar el paquete jest-snapshot. Podemos hacerlo con el siguiente comando:

yarn add jest-snapshot

Una vez instalado el paquete, podemos habilitar el snapshot testing en el archivo jest.config.js. Para ello, agregamos la siguiente línea al archivo:

snapshotSerializers: [
   'jest-snapshot/serialize',
   'jest-snapshot/snapshotSerializer'
],

La configuración snapshotSerializers se agrega en el archivo de configuración de Jest, que generalmente se llama jest.config.js. Este archivo se encuentra en la raíz de tu proyecto o en una ubicación especificada en tu configuración.

Para agregar la configuración snapshotSerializers, debes asegurarte de que el objeto de configuración de Jest en jest.config.js contenga la propiedad snapshotSerializers. Si aún no tienes un archivo jest.config.js, puedes crearlo en la raíz de tu proyecto.

A continuación, se muestra un ejemplo de cómo agregar la configuración snapshotSerializers en jest.config.js:

module.exports = {
  // Otras configuraciones de Jest...

  // Agrega snapshotSerializers aquí
  snapshotSerializers: [
    'jest-snapshot/serialize',
    'jest-snapshot/snapshotSerializer'
  ],
};

Esto habilitará el snapshot testing para todos los tests de la aplicación.

¿Por qué deberías utilizar Snapshots?

El uso de Snapshots en tus pruebas de software ofrece una serie de beneficios y ventajas que hacen que esta técnica sea una elección sólida para muchos proyectos. Aquí hay algunas razones adicionales para considerar su adopción:

Documentación visual en vivo

Las instantáneas actúan como una documentación visual en vivo de tu aplicación. Proporcionan una representación actualizada de cómo se supone que se ve y se comporta la interfaz de usuario en diferentes estados. Esto es especialmente útil para equipos de desarrollo y diseño que desean mantener una comprensión clara de la apariencia actual de la aplicación.

Relación con otras herramientas:

  • Storybook: Storybook es una herramienta de desarrollo de componentes que permite a los equipos de diseño y desarrollo visualizar y probar componentes de forma aislada. Si bien Storybook se enfoca en la creación de componentes y su documentación interactiva, el Snapshot Testing complementa esta funcionalidad al proporcionar una visión estática y automatizada de cómo se ven esos componentes en diferentes estados.

  • Herramientas de documentación de diseño: En proyectos que involucran diseño, a menudo se utilizan herramientas de documentación de diseño como Sketch, Figma o Adobe XD. Estas herramientas permiten a los diseñadores crear diseños visuales y documentación. El Snapshot Testing puede actuar como un puente entre estas herramientas y el código, ya que garantiza que la implementación del diseño en el código sea coherente con la visión del diseñador.

  • Control de versiones y seguimiento de cambios: Las instantáneas generadas por el Snapshot Testing también pueden ser rastreadas en sistemas de control de versiones como Git. Esto facilita la comprensión de cómo ha evolucionado la interfaz de usuario y cómo han cambiado las salidas de funciones con el tiempo.

Facilita las revisiones de código

Cuando se realiza una revisión de código, las instantáneas proporcionan una base objetiva para discutir y evaluar los cambios propuestos. Los revisores pueden ver visualmente las diferencias entre las instantáneas previas y actuales, lo que facilita la identificación de cambios intencionados y no intencionados.

Un ejemplo simplificado de cómo facilita las revisiones de código (includo en la detección de cambios inesperados):

1. Un componente de React antes de los cambios

import React from 'react';

const Boton = () => {
  return <button>Haz clic</button>;
};

export default Boton;

2. Prueba de Snapshot para el componente antes de los cambios

import React from 'react';
import { shallow } from 'enzyme';
import Boton from './Boton';

test('Renderiza el botón correctamente', () => {
  const wrapper = shallow(<Boton />);
  expect(wrapper).toMatchSnapshot();
});

En este punto, hemos ejecutado la prueba y hemos guardado la instantánea resultante en un archivo.

Luego, supongamos que realizamos un cambio en el componente de React. Por ejemplo, cambiamos el texto del botón:

3. Componente de React después de los cambios

import React from 'react';

const Boton = () => {
  return <button>Haz clic aquí</button>; // Cambio en el texto del botón
};

export default Boton;

Ahora, volvemos a ejecutar la prueba de Snapshot. La prueba fallará, ya que la instantánea previamente almacenada no coincide con la salida actual del componente, que ha cambiado.

La salida de la prueba después de los cambios mostrará la diferencia entre la instantánea almacenada previamente y la salida actual, lo que facilita la revisión de código.

Detección temprana de cambios inesperados

Es altamente efectivo para detectar cambios inesperados en la interfaz de usuario o en la lógica de la aplicación. Cualquier alteración en la apariencia de un componente o en la salida de una función se destacará de inmediato, lo que permite una corrección temprana antes de que los problemas se propaguen más allá de la etapa de desarrollo.

Si prestas atención a mis ejemplos, notarás que he utilizado Enzyme, que es una biblioteca de utilidades para pruebas de componentes de React que a menudo se utiliza en combinación con Jest, pero no es necesario instalarlo junto con Jest Snapshot.

Sin embargo, si deseas realizar pruebas más avanzadas en componentes de React, como simulaciones de eventos y montaje completo de componentes, Enzyme es una opción popular y puede complementar Jest para pruebas de componentes de React. En ese caso, sí necesitarías instalar y configurar Enzyme por separado, pero no es un requisito para el Snapshot Testing en sí.

¿Cuándo usarlo?

El snapshot testing es una buena opción para probar componentes de UI que son difíciles de probar de forma unitaria. Por ejemplo, los componentes que dependen de datos externos o que interactúan con otros componentes.

El snapshot testing también es una buena opción para probar la funcionalidad de una aplicación que es difícil de definir de forma unitaria. Por ejemplo, la funcionalidad que depende de la interacción del usuario.

Cómo y con qué frecuencia actualizar las instantáneas

La actualización de instantáneas debe realizarse de manera controlada y planificada, no de forma automática o aleatoria. La idea es que actualices las instantáneas cuando hagas cambios en el código que afecten la salida esperada de las pruebas. Aquí hay algunas pautas:

  1. Cambios intencionados: Debes actualizar las instantáneas cuando realices cambios en tu componente o función de forma intencionada y esperes que la salida cambie. Por ejemplo, si modificas un componente para cambiar el texto de un botón de "Haz clic" a "Presiona aquí", debes actualizar la instantánea.

  2. Actualizaciones planificadas: Es una buena práctica planificar actualizaciones periódicas de instantáneas, incluso si no realizaste cambios visuales en tu código. Esto ayuda a mantener las instantáneas actualizadas con la última representación visual de tus componentes y funciones. Por ejemplo, podrías programar actualizaciones de instantáneas antes de cada lanzamiento importante de tu aplicación.

  3. Actualizaciones después de cambios significativos: Después de realizar cambios significativos en la lógica de tu aplicación o en la representación visual, es importante actualizar las instantáneas para asegurarte de que reflejen la salida esperada.

Por lo que si se desea actualizar el snapshot, puedes utilizar el siguiente comando:

jest --updateSnapshot

Este comando actualiza todas las instantáneas en tu proyecto según la salida actual. Puedes ejecutarlo manualmente cuando sea necesario después de realizar cambios, o como parte de tus flujos de trabajo de CI/CD (integración continua / entrega continua) para mantener actualizadas las instantáneas en cada confirmación o lanzamiento.

Ventajas y desventajas

Ventajas:

  • Es fácil de configurar y usar.
  • Es rápido y eficiente.
  • Es útil para probar componentes de UI que son difíciles de probar de forma unitaria.
  • Es útil para probar la funcionalidad de una aplicación que es difícil de definir de forma unitaria.

Desventajas:

  • No es adecuado para probar la funcionalidad de una aplicación que cambia con frecuencia.
  • Puede ser difícil identificar el origen de un error si el snapshot no coincide.

Cuando una prueba de Snapshot falla, esto significa que la salida actual difiere de la instantánea previamente almacenada. En este punto, el reto puede ser identificar la causa exacta de la discrepancia, especialmente si el cambio es inesperado o no estaba previsto.

La dificultad para identificar la causa radica en que la diferencia se muestra en forma de una representación visual o serializada de los datos, pero no proporciona información detallada sobre cuál fue el cambio en el código que condujo a esa diferencia. Por lo tanto, los desarrolladores pueden enfrentar desafíos para rastrear y corregir el problema subyacente.

Por esta razón, construir componentes pequeños y testeables es sumamente importante.

Ejemplos prácticos

Prueba de un componente de UI

import React from 'react';
import { shallow } from 'enzyme';

const MyComponent = () => {
  return (
    <div>
      <h1>Hola mundo</h1>
    </div>
  );
};

describe('MyComponent', () => {
  it('renderiza el componente correctamente', () => {
    const wrapper = shallow(<MyComponent />);

    expect(wrapper).toMatchSnapshot();
  });
});

Este ejemplo prueba un componente de UI simple que renderiza un encabezado. El test toma una instantánea del componente renderizado y la compara con una instantánea anterior. Si el componente se ha actualizado, la nueva instantánea se guardará y el test pasará. Si el componente no se ha actualizado, el test fallará.

Y la posible salida de esta prueba se vería de la siguiente manera:

Received value does not match stored snapshot

- Snapshot
+ Received

<div>
  <h1>
    Hola mundo
  </h1>
</div>

Las secciones clave de la salida:

  • Snapshot: Esta sección muestra la instantánea almacenada previamente. En este caso, representa cómo se veía el componente MyComponent en una ejecución anterior de la prueba.

  • Received: Esta sección muestra la salida actual del componente MyComponent durante la ejecución actual de la prueba. Aquí, se muestra el JSX resultante del componente.

La diferencia entre el "Snapshot" y el "Received" muestra que ha habido un cambio en la salida del componente desde la última vez que se ejecutó la prueba. En este caso, el cambio detectado es la adición de espacios en blanco y tabulaciones en el JSX.

Cuando se produce una discrepancia entre el "Snapshot" y el "Received", se considera un fallo de prueba. En este punto, el desarrollador debe revisar el cambio en el JSX para determinar si es un cambio legítimo y previsto o si se trata de un cambio inesperado que requiere corrección.

En caso de que la prueba hubiese funcionado, la salida sería ligeramente diferente:

En caso de que la prueba sea correcta, la salida será similar a la siguiente:

Snapshot match successfully!

Stored snapshot: 

<div>
  <h1>
    Hola mundo
  </h1>
</div>

Ejemplo 2: Prueba de la funcionalidad de una aplicación

import { add } from './calculator';

describe('add', () => {
  it('devuelve la suma de dos números', () => {
    expect(add(1, 2)).toBe(3);
  });

  it('devuelve la suma de tres números', () => {
    expect(add(1, 2, 3)).toBe(6);
  });
});

Este ejemplo prueba la funcionalidad de una función que suma dos números. El test toma una instantánea de la salida de la función y la compara con una instantánea anterior. Si la salida de la función cambia, el test fallará.

Y solo para dejar claro los formatos que se usan para guardar estas salidas:

  • Si estás probando un componente de interfaz de usuario, la instantánea podría ser un fragmento de JSX o HTML serializado.

  • Si estás probando una función que devuelve un objeto o una cadena de texto, la instantánea será una representación serializada de ese resultado.


IMPORTANTE:

La elección entre shallow, mount, o render dependerá de tus necesidades específicas de prueba y de la complejidad de los componentes que estás probando:

  1. shallow: Este método de Enzyme se utiliza para crear una representación superficial del componente, lo que significa que no renderiza completamente los componentes secundarios. Es útil cuando deseas probar principalmente el componente en sí y no te preocupas por los detalles internos de los componentes secundarios.

  2. mount: Este método de Enzyme renderiza completamente el componente y todos sus componentes secundarios. Es útil cuando necesitas realizar pruebas que involucran la interacción de componentes secundarios o cuando deseas probar el montaje completo del componente.

  3. render: Jest proporciona el método render para renderizar componentes de React de manera similar a shallow. Es una opción nativa para realizar pruebas de Snapshot sin Enzyme. Puedes usarlo cuando deseas una representación más profunda que shallow pero no necesitas la representación completa de mount.

Si decides no utilizar shallow o Enzyme, podrías hacer algo como esto en Jest para realizar pruebas de Snapshot:


import React from 'react';
import renderer from 'react-test-renderer'; // Importa el módulo renderer de Jest

import Boton from './Boton'; // Importa el componente a probar

test('Renderiza el componente correctamente', () => {
  const component = renderer.create(<Boton />); // Renderiza el componente
  const tree = component.toJSON(); // Convierte la representación en un objeto JSON
  expect(tree).toMatchSnapshot(); // Compara con una instantánea previamente almacenada
});

Conclusión

El snapshot testing es una técnica de pruebas de software que puede ser muy útil para probar componentes de UI y funcionalidad de aplicaciones.

Algo que es importante mencionar antes que terminemos con esto; las instantáneas generadas por el Snapshot Testing deben guardarse en el repositorio de código junto con las pruebas correspondientes. De esta manera, tienes un punto de partida para comparar las salidas actuales de tus componentes de la interfaz de usuario o funciones con las instantáneas almacenadas previamente.

En este artículo exploré jest-snapshot, pero también existe react-test-renderer, ambas librerías se utilizan para crear pruebas de instantáneas en React. Sin embargo, hay algunas diferencias clave entre ambas bibliotecas:

  • jest-snapshot es una biblioteca independiente de Jest, mientras que react-test-renderer es una biblioteca proporcionada por Jest.
  • jest-snapshot solo se puede utilizar para crear pruebas de instantáneas, mientras que react-test-renderer también se puede utilizar para otros fines, como la creación de componentes de prueba.
  • jest-snapshot es más fácil de usar que react-test-renderer, ya que solo requiere una línea de código para crear una instantánea.

Happy coding! :D


Photo by Marco Bianchetti on Unsplash

Jack Fiallos

Jack Fiallos

Te gustó este artículo?