Pruebas unitarias en aplicaciones nativas React

Publicado: 2022-03-10
Resumen rápido ↬ Las pruebas unitarias se han convertido en una parte integral del proceso de desarrollo de software. Es el nivel de prueba en el que se prueban los componentes del software. En este tutorial, aprenderá cómo probar unidades de una aplicación React Native.

React Native es uno de los marcos más utilizados para crear aplicaciones móviles. Este tutorial está dirigido a desarrolladores que desean comenzar a probar las aplicaciones React Native que crean. Haremos uso del marco de pruebas Jest y Enzyme.

En este artículo, aprenderemos los principios básicos de las pruebas, exploraremos varias bibliotecas para probar una aplicación y veremos cómo probar unidades (o componentes) de una aplicación React Native. Al trabajar con una aplicación React Native, consolidaremos nuestro conocimiento de las pruebas.

Nota: El conocimiento básico de JavaScript y React Native sería de gran beneficio mientras trabaja en este tutorial.

¿Qué es la prueba unitaria?

La prueba unitaria es el nivel de prueba en el que se prueban los componentes individuales del software. Lo hacemos para asegurarnos de que cada componente funcione como se espera. Un componente es la parte comprobable más pequeña del software.

Para ilustrar, creemos un componente Button y simulemos una prueba unitaria:

 import React from 'react'; import { StyleSheet, Text, TouchableOpacity } from 'react-native'; function AppButton({ onPress }) { return ( <TouchableOpacity style={[styles.button, { backgroundColor: colors[color] }]} onPress={onPress} > <Text style={styles.text}>Register</Text> </TouchableOpacity> ); } const styles = StyleSheet.create({ button: { backgroundColor: red; borderRadius: 25, justifyContent: 'center', alignItems: 'center', }, text: { color: #fff } }) export default AppButton;

Este componente Button tiene texto y una función onPress . Probemos este componente para ver de qué se trata la prueba unitaria.

Primero, creemos un archivo de prueba, llamado Button.test.js :

 it('renders correctly across screens', () => { const tree = renderer.create(<Button />).toJSON(); expect(tree).toMatchSnapshot(); });

Aquí, estamos probando para ver si nuestro componente Button se muestra como debería en todas las pantallas de la aplicación. De esto se tratan las pruebas unitarias: probar los componentes de una aplicación para asegurarse de que funcionan como deberían.

Pruebas unitarias en aplicaciones nativas React

Una aplicación React Native se puede probar con una variedad de herramientas, algunas de las cuales son las siguientes:

  • WebDriver
    Esta herramienta de prueba de código abierto para aplicaciones Node.js también se usa para probar aplicaciones React Native.
  • Pesadilla
    Esto automatiza las operaciones de prueba en el navegador. De acuerdo con la documentación, "el objetivo es exponer algunos métodos simples que imiten las acciones del usuario (como goto a, type y click ), con una API que se siente sincrónica para cada bloque de secuencias de comandos, en lugar de devoluciones de llamada profundamente anidadas".
  • Broma
    Esta es una de las bibliotecas de prueba más populares que existen y en la que nos centraremos hoy. Al igual que React, es mantenido por Facebook y se creó para proporcionar una configuración de "configuración cero" para un rendimiento máximo.
  • Moca
    Mocha es una biblioteca popular para probar aplicaciones React y React Native. Se ha convertido en una herramienta de prueba de elección para los desarrolladores debido a lo fácil que es configurar y usar y lo rápido que es.
  • Jazmín
    Según su documentación, Jasmine es un marco de desarrollo basado en el comportamiento para probar el código JavaScript.
¡Más después del salto! Continúe leyendo a continuación ↓

Introducción a la broma y la enzima

Según su documentación, "Jest es un marco de prueba de JavaScript encantador que se centra en la simplicidad". Funciona con configuración cero. Tras la instalación (usando un administrador de paquetes como npm o Yarn), Jest está listo para usar, sin necesidad de otras instalaciones.

Enzyme es un marco de prueba de JavaScript para aplicaciones React Native. (Si está trabajando con React en lugar de React Native, hay una guía disponible). Usaremos Enzyme para probar unidades de la salida de nuestra aplicación. Con él podemos simular el tiempo de ejecución de la aplicación.

Comencemos configurando nuestro proyecto. Usaremos la aplicación Done With It en GitHub. Es un mercado de aplicaciones React Native. Comience por clonarlo, navegue a la carpeta e instale los paquetes ejecutando lo siguiente para npm...

 npm install

… o esto para Hilo:

 yarn install

Este comando instalará todos los paquetes en nuestra aplicación. Una vez hecho esto, probaremos la consistencia de la interfaz de usuario de nuestra aplicación usando instantáneas, que se describen a continuación.

Instantáneas y configuración de Jest

En esta sección, probaremos los toques del usuario y la interfaz de usuario de los componentes de la aplicación probando instantáneas con Jest.

Antes de hacer eso, necesitamos instalar Jest y sus dependencias. Para instalar Jest para Expo React Native, ejecute el siguiente comando:

 yarn add jest-expo --dev

Esto instala jest-expo en el directorio de nuestra aplicación. A continuación, debemos actualizar nuestro archivo package.json para tener un script de prueba:

 "scripts": { "test" "jest" }, "jest": { "preset": "jest-expo" }

Al agregar este comando, le estamos diciendo a Jest qué paquete registrar en nuestra aplicación y dónde.

Lo siguiente es agregar otros paquetes a nuestra aplicación que ayudarán a Jest a realizar una prueba exhaustiva. Para npm, ejecuta esto...

 npm i react-test-renderer --save-dev

… y para Yarn, esto:

 yarn add react-test-renderer --dev

Todavía tenemos una pequeña configuración que hacer en nuestro archivo package.json . De acuerdo con la documentación de Expo React Native, necesitamos agregar una configuración transformIgnorePattern que evite que las pruebas se ejecuten en Jest cada vez que un archivo fuente coincida con una prueba (es decir, si se realiza una prueba y se encuentra un archivo similar en los node modules del proyecto).

 "jest": { "preset": "jest-expo", "transformIgnorePatterns": [ "node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|expo(nent)?|@expo(nent)?/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|@sentry/.*)" ] }

Ahora, creemos un nuevo archivo, llamado App.test.js , para escribir nuestra primera prueba. Probaremos si nuestra App tiene un elemento secundario en su árbol:

 import React from "react"; import renderer from "react-test-renderer"; import App from "./App.js" describe("<App />", () => { it('has 1 child', () => { const tree = renderer.create(<App />).toJSON(); expect(tree.children.length).toBe(1); }); });

Ahora, ejecute yarn test o su equivalente npm. Si App.js tiene un solo elemento secundario, nuestra prueba debería pasar, lo que se confirmará en la interfaz de línea de comandos.

En el código anterior, hemos importado React y react-test-renderer , que representa nuestras pruebas para Expo . Hemos convertido el árbol de componentes <App /> a JSON, y luego le pedimos a Jest que verifique si la cantidad de componentes secundarios devueltos en JSON es igual a lo que esperábamos.

Más pruebas de instantáneas

Como afirma David Adeneye:

“Una prueba instantánea asegura que la interfaz de usuario (UI) de una aplicación web no cambie inesperadamente. Captura el código de un componente en un momento en el tiempo, para que podamos comparar el componente en un estado con cualquier otro estado posible que pueda tener”.

Esto se hace especialmente cuando un proyecto involucra estilos globales que se usan en muchos componentes. Escribamos una prueba de instantánea para App.js para probar la consistencia de la interfaz de usuario:

 it('renders correctly across screens', () => { const tree = renderer.create( ).toJSON(); expect(tree).toMatchSnapshot(); }); it('renders correctly across screens', () => { const tree = renderer.create( ).toJSON(); expect(tree).toMatchSnapshot(); });

Agregue esto a las pruebas que ya hemos escrito y luego ejecute yarn test (o su equivalente npm). Si nuestra prueba pasa, deberíamos ver esto:

 PASS src/App.test.js √ has 1 child (16ms) √ renders correctly (16ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 1 total Time: 24s

Esto nos dice que nuestras pruebas pasaron y el tiempo que tardaron. Su resultado será similar si pasa las pruebas.

Pasemos a burlarnos de algunas funciones en Jest.

Burlarse de las llamadas a la API

Según la documentación de Jest:

Las funciones simuladas le permiten probar los vínculos entre el código borrando la implementación real de una función, capturando llamadas a la función (y los parámetros pasados ​​en esas llamadas), capturando instancias de funciones de constructor cuando se instancian con `nuevo` y permitiendo pruebas configuración temporal de los valores de retorno.

En pocas palabras, un simulacro es una copia de un objeto o función sin el funcionamiento real de esa función. Imita esa función.

Los simulacros nos ayudan a probar aplicaciones de muchas maneras, pero el principal beneficio es que reducen nuestra necesidad de dependencias.

Los simulacros generalmente se pueden realizar de una de dos maneras. Una es crear una función simulada que se inyecta en el código que se va a probar. La otra es escribir una función simulada que invalide el paquete o la dependencia que se adjunta al componente.

La mayoría de las organizaciones y desarrolladores prefieren escribir simulacros manuales que imitan la funcionalidad y usan datos falsos para probar algunos componentes.

React Native incluye fetch en el objeto global. Para evitar hacer llamadas API reales en nuestra prueba unitaria, las imitamos. A continuación se muestra una forma de simular todas, si no la mayoría, de nuestras llamadas API en React Native, y sin necesidad de dependencias:

 global.fetch = jest.fn(); // mocking an API success response once fetch.mockResponseIsSuccess = (body) => { fetch.mockImplementationForOnce ( () => Promise.resolve({json: () => Promise.resolve(JSON.parse(body))}) ); }; // mocking an API failure response for once fetch.mockResponseIsFailure = (error) => { fetch.mockImplementationForOnce( () => Promise.reject(error) ); };

Aquí, hemos escrito una función que intenta obtener una API una vez. Habiendo hecho esto, devuelve una promesa, y cuando se resuelve, devuelve el cuerpo en JSON. Es similar a la respuesta simulada para una transacción de recuperación fallida: devuelve un error.

A continuación se muestra el componente de product de nuestra aplicación, que contiene un objeto de product y devuelve la información como props .

 import React from 'react'; const Product = () => { const product = { name: 'Pizza', quantity: 5, price: '$50' } return ( <> <h1>Name: {product.name}</h1> <h1>Quantity: {product.quantity}</h1> <h1>Price: {product.price}</h1> </> ); } export default Product;

Imaginemos que estamos tratando de probar todos los componentes de nuestro producto. Acceder directamente a nuestra base de datos no es una solución factible. Aquí es donde entran en juego las burlas. En el código a continuación, intentamos simular un componente del producto usando Jest para describir los objetos en el componente.

 describe("", () => { it("accepts products props", () => { const wrapper = mount(<Customer product={product} />); expect(wrapper.props().product).toEqual(product); }); it("contains products quantity", () => { expect(value).toBe(3); }); });

Estamos usando describe de Jest para dictar las pruebas que queremos que se hagan. En la primera prueba, estamos comprobando si el objeto que estamos pasando es igual a los accesorios que hemos burlado.

En la segunda prueba, estamos pasando los accesorios del customer para asegurarnos de que sea un producto y que coincida con nuestras simulaciones. Al hacerlo, no tenemos que probar todos los componentes de nuestro producto y también podemos evitar errores en nuestro código.

Simulación de solicitudes de API externas

Hasta ahora, hemos estado realizando pruebas para llamadas API con otros elementos de nuestra aplicación. Ahora vamos a simular una llamada API externa. Vamos a utilizar Axios. Para probar una llamada externa a una API, debemos simular nuestras solicitudes y también administrar las respuestas que recibimos. Vamos a usar axios-mock-adapter para simular Axios. Primero, necesitamos instalar axios-mock-adapter ejecutando el siguiente comando:

 yarn add axios-mock-adapter

Lo siguiente que debemos hacer es crear nuestros simulacros:

 import MockAdapter from 'axios-mock-adapter'; import Faker from 'faker' import ApiClient from '../constants/api-client'; import userDetails from 'jest/mockResponseObjects/user-objects'; let mockApi = new MockAdapter(ApiClient.getAxiosInstance()); let validAuthentication = { name: Faker.internet.email(), password: Faker.internet.password() mockApi.onPost('requests').reply(config) => { if (config.data === validAuthentication) { return [200, userDetails]; } return [400, 'Incorrect username and password']; });

Aquí, llamamos a ApiClient y le pasamos una instancia de Axios para simular las credenciales del usuario. Estamos usando un paquete llamado faker.js para generar datos de usuario falsos, como una dirección de correo electrónico y una contraseña.

El simulacro se comporta como esperamos que lo haga la API. Si la solicitud es exitosa, obtendremos una respuesta con un código de estado de 200 para OK. Y obtendremos un código de estado de 400 para una solicitud incorrecta al servidor, que se enviaría con JSON con el mensaje "Nombre de usuario y contraseña incorrectos".

Ahora que nuestro simulacro está listo, escribamos una prueba para una solicitud de API externa. Como antes, usaremos instantáneas.

 it('successful sign in with correct credentials', async () => { await store.dispatch(authenticateUser('[email protected]', 'password')); expect(getActions()).toMatchSnapshot(); }); it('unsuccessful sign in with wrong credentials', async () => { await store.dispatch(authenticateUser('[email protected]', 'wrong credential')) .catch((error) => { expect(errorObject).toMatchSnapshot(); });

Aquí, estamos probando un inicio de sesión exitoso con las credenciales correctas, utilizando la async await nativa de JavaScript para contener nuestras entradas. Mientras tanto, la función authenticateUser de Jest autentica la solicitud y se asegura de que coincida con nuestras instantáneas anteriores. A continuación, probamos un inicio de sesión fallido en caso de credenciales incorrectas, como la dirección de correo electrónico o la contraseña, y enviamos un error como respuesta.

Ahora, ejecute la prueba de yarn test o npm test . Estoy seguro de que todas sus pruebas pasarán.

Veamos cómo probar componentes en una biblioteca de administración de estado, Redux.

Prueba de acciones y reductores de Redux usando instantáneas

No se puede negar que Redux es uno de los administradores de estado más utilizados para aplicaciones React. La mayor parte de la funcionalidad en Redux implica un dispatch , que es una función de la tienda de Redux que se utiliza para activar un cambio en el estado de una aplicación. Probar Redux puede ser complicado porque las actions de Redux crecen rápidamente en tamaño y complejidad. Con las instantáneas de Jest, esto se vuelve más fácil. La mayoría de las pruebas con Redux se reducen a dos cosas:

  • Para probar actions , creamos redux-mock-store y despachamos las acciones.
  • Para probar los reductores, importamos el reducer y le pasamos un objeto de estado y acción.

A continuación se muestra una prueba de Redux con instantáneas. Probaremos las acciones enviadas al autenticar al usuario en el SIGN-IN y ver cómo el reductor del user maneja la acción LOGOUT .

 import mockStore from 'redux-mock-store'; import { LOGOUT } from '../actions/logout'; import User from '../reducers/user'; import { testUser } from 'jest/mock-objects'; describe('Testing the sign in authentication', () => { const store = mockStore(); it('user attempts with correct password and succeeds', async () => { await store.dispatch(authenticateUser('[email protected]', 'password')); expect(store.getActions()).toMatchSnapshot(); }); }); describe('Testing reducers after user LOGS OUT', () => { it('user is returned back to initial app state', () => { expect(user(testUser, { type: LOGOUT })).toMatchSnapshot(); }); });

En la primera prueba, describimos la autenticación de inicio de sesión y creamos una tienda simulada. Hacemos esto primero importando un mockStore de Redux y luego importando un método llamado testUser de Jest para ayudarnos a burlarnos de un usuario. A continuación, comprobamos cuándo el usuario inicia sesión correctamente en la aplicación utilizando una dirección de correo electrónico y una contraseña que coincidan con las de nuestra tienda de instantáneas. Por lo tanto, la instantánea garantiza que los objetos que ingresa el usuario coincidan cada vez que se ejecuta una prueba.

En la segunda prueba, estamos probando cuándo el usuario cierra la sesión. Una vez que nuestra instantánea de reducción confirma que un usuario ha cerrado sesión, vuelve al estado inicial de la aplicación.

A continuación, probamos ejecutando yarn test . Si las pruebas han pasado, deberíamos ver el siguiente resultado:

 PASS src/redux/actions.test.js √ user attempts with correct password and succeeds (23ms) √ user is returned back to initial app state (19ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 2 total Time: 31s

Conclusión

Con Jest, probar las aplicaciones React Native nunca ha sido tan fácil, especialmente con las instantáneas, que aseguran que la interfaz de usuario permanezca consistente independientemente de los estilos globales. Además, Jest nos permite simular ciertas llamadas y módulos API en nuestra aplicación. Podemos llevar esto más allá probando los componentes de una aplicación React Native.

Más recursos

  • “Una guía práctica para probar aplicaciones nativas de React con Jest”, David Adeneye, Smashing Magazine
  • Documentación de broma
  • "Prueba con broma", documentación de Expo React Native
  • “Aprender a probar React Native con Jest”, Jason Gaare