Saltar al contenido principal
Versión: 0.80

Pruebas

Traducción Beta No Oficial

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

A medida que tu base de código crece, pequeños errores y casos límite inesperados pueden escalar a fallos mayores. Los bugs generan mala experiencia de usuario y, en última instancia, pérdidas comerciales. Una forma de prevenir programación frágil es probar tu código antes de lanzarlo a producción.

En esta guía, cubriremos diferentes métodos automatizados para garantizar que tu app funcione como se espera, desde análisis estático hasta pruebas de extremo a extremo.

Testing is a cycle of fixing, testing, and either passing to release or failing back into testing.

Por qué realizar pruebas

Somos humanos, y los humanos cometemos errores. Las pruebas son importantes porque te ayudan a descubrir estos errores y verifican que tu código funciona. Quizás aún más importante: las pruebas garantizan que tu código siga funcionando en el futuro al agregar nuevas funciones, refactorizar las existentes o actualizar dependencias principales de tu proyecto.

Las pruebas tienen más valor del que podrías imaginar. Una de las mejores formas de corregir un bug es escribir una prueba que falle y lo exponga. Luego, cuando arreglas el error y vuelves a ejecutar la prueba, si pasa significa que el bug está corregido y nunca se reintroducirá en la base de código.

Las pruebas también sirven como documentación para nuevos miembros del equipo. Para quienes nunca han visto una base de código, leer los tests les ayuda a comprender cómo funciona el código existente.

Last but not least, more automated testing means less time spent with manual QA, freeing up valuable time.

Análisis estático

El primer paso para mejorar la calidad de tu código es usar herramientas de análisis estático. Este análisis verifica errores en tu código mientras lo escribes, sin necesidad de ejecutarlo.

  • Los Linters analizan el código para detectar errores comunes como código no utilizado, ayudar a evitar trampas y marcar violaciones de la guía de estilos (como usar tabulaciones en lugar de espacios o viceversa, según tu configuración).

  • La verificación de tipos asegura que lo que pasas a una función coincida con lo que está diseñada para aceptar, evitando por ejemplo pasar un string a una función de conteo que espera un número.

React Native incluye dos herramientas configuradas listas para usar: ESLint para linting y TypeScript para verificación de tipos.

Escribiendo código testeable

Para comenzar con pruebas, primero necesitas escribir código testeable. Considera el proceso de fabricación de aviones: antes de que cualquier modelo despegue para demostrar que todos sus sistemas complejos funcionan juntos, se prueban partes individuales para garantizar seguridad y funcionamiento. Por ejemplo: las alas se prueban doblando bajo carga extrema; las piezas del motor por durabilidad; el parabrisas contra impactos de pájaros simulados.

El software es similar. En lugar de escribir todo tu programa en un archivo enorme con miles de líneas, escribe tu código en módulos pequeños que puedas probar más exhaustivamente que el conjunto completo. Así, escribir código testeable está entrelazado con escribir código limpio y modular.

Para hacer tu app más testeable, comienza separando la parte visual (tus componentes React) de la lógica de negocio y el estado de la app (independientemente de si usas Redux, MobX u otras soluciones). Así mantienes las pruebas de lógica de negocio (que no deberían depender de componentes React) independientes de los componentes mismos, cuyo trabajo es principalmente renderizar la UI.

Teóricamente, podrías llevar esto hasta mover toda la lógica y obtención de datos fuera de tus componentes. Así tus componentes se dedicarían exclusivamente a renderizar. Tu estado sería completamente independiente de los componentes. ¡La lógica de tu app funcionaría sin ningún componente React!

Te animamos a explorar más sobre código testeable en otros recursos de aprendizaje.

Escribiendo pruebas

Después de escribir código testeable, ¡es hora de escribir pruebas reales! La plantilla predeterminada de React Native incluye el framework de pruebas Jest. Viene con un preset adaptado a este entorno para que puedas ser productivo sin ajustar configuraciones ni mocks inmediatamente—más sobre mocks en breve. Puedes usar Jest para escribir todo tipo de pruebas mencionadas en esta guía.

Si practicas desarrollo guiado por pruebas (TDD), ¡escribes las pruebas primero! Así garantizas la testabilidad de tu código desde el inicio.

Estructuración de pruebas

Tus pruebas deben ser concisas y preferiblemente probar solo un aspecto. Comencemos con un ejemplo de prueba unitaria escrita con Jest:

js
it('given a date in the past, colorForDueDate() returns red', () => {
expect(colorForDueDate('2000-10-20')).toBe('red');
});

La prueba se describe mediante el string pasado a la función it. Cuida bien la redacción de la descripción para que quede claro qué se está probando. Intenta cubrir estos elementos:

  1. Dado - alguna precondición

  2. Cuando - alguna acción ejecutada por la función que estás probando

  3. Entonces - el resultado esperado

Esto también se conoce como AAA (Arrange, Act, Assert en inglés).

Jest ofrece la función describe para estructurar pruebas. Úsala para agrupar pruebas relacionadas con una misma funcionalidad. Los bloques describe pueden anidarse si es necesario. Otras funciones comunes son beforeEach o beforeAll para configurar objetos bajo prueba. Más detalles en la referencia de API de Jest.

Si una prueba tiene muchos pasos o expectativas, conviene dividirla en pruebas más pequeñas. Además, asegura que sean completamente independientes: cada prueba debe ejecutarse de forma aislada sin depender de otras. Por otro lado, al ejecutar todo el conjunto, una prueba no debe afectar los resultados de otra.

Finalmente, como desarrolladores nos gusta que nuestro código funcione perfectamente. Con las pruebas, ¡a menudo es lo contrario! Considera una prueba fallida como algo positivo: indica que algo necesita atención. Esto te da oportunidad de corregir problemas antes que afecten a los usuarios.

Pruebas unitarias

Las pruebas unitarias cubren las partes más pequeñas del código, como funciones individuales o clases.

Cuando el objeto probado tiene dependencias externas, frecuentemente necesitarás simularlas (mocks), como explicaremos a continuación.

Su gran ventaja es que son rápidas de escribir y ejecutar. Así obtienes retroalimentación inmediata sobre si tus pruebas pasan. Jest incluso permite ejecutar continuamente pruebas relacionadas con el código que editas: Modo Watch.

Simulación (Mocking)

Cuando tus objetos bajo prueba tienen dependencias externas, a veces necesitarás "simularlas". "Mocking" significa reemplazar una dependencia con tu propia implementación controlada.

Generalmente, usar objetos reales en pruebas es mejor que usar mocks, pero hay situaciones donde esto no es posible. Por ejemplo: cuando tu prueba unitaria JS depende de un módulo nativo escrito en Java u Objective-C.

Imagina una app que muestra el clima actual usando un servicio externo. Si el servicio indica que está lloviendo, muestras una imagen de nube con lluvia. No quieres llamar al servicio real en tus pruebas porque:

  • Haría las pruebas lentas e inestables (por las solicitudes de red)

  • El servicio podría devolver datos diferentes en cada ejecución

  • ¡Los servicios de terceros pueden fallar justo cuando necesitas ejecutar pruebas!

Por ello, puedes implementar un servicio simulado (mock), reemplazando efectivamente miles de líneas de código ¡y hasta termómetros conectados a internet!

Jest incluye soporte para mocking desde nivel de función hasta nivel de módulo.

Pruebas de Integración

Al desarrollar sistemas de software complejos, sus componentes individuales necesitan interactuar entre sí. En pruebas unitarias, cuando una unidad depende de otra, a veces terminas simulando (mocking) esa dependencia, reemplazándola con una falsa.

En pruebas de integración, combinas unidades reales (como en tu aplicación) y las pruebas juntas para verificar que colaboran correctamente. Esto no significa que no uses simulaciones: aún necesitarás mocks (por ejemplo, para simular un servicio meteorológico), pero en menor medida que en pruebas unitarias.

Nota: La terminología sobre pruebas de integración no siempre es consistente. Además, la línea entre prueba unitaria y de integración puede ser difusa. Para esta guía, tu prueba es "de integración" si:

  • Combina varios módulos de tu app como se describió
  • Usa un sistema externo
  • Realiza llamadas de red a otras aplicaciones (como una API de servicio meteorológico)
  • Hace cualquier tipo de I/O de archivos o base de datos

Pruebas de Componentes

Los componentes de React renderizan tu app y los usuarios interactúan directamente con ellos. Incluso con lógica de negocio bien probada, sin pruebas de componentes podrías entregar una UI defectuosa. Estas pruebas podrían considerarse unitarias o de integración, pero por su importancia en React Native, las tratamos por separado.

Para probar componentes de React, debes evaluar dos aspectos:

  • Interacción: verificar que el componente responde correctamente a acciones de usuario (ej. presionar un botón)

  • Renderizado: asegurar que la salida visual es correcta (ej. apariencia y posición del botón en la UI)

Por ejemplo, para un botón con listener onPress, debes probar tanto su apariencia como el manejo correcto del evento al pulsarlo.

Algunas bibliotecas útiles:

  • React Native Testing Library extiende el renderizador de React con APIs fireEvent y query que describiremos.

  • [Obsoleto] El Test Renderer de React, desarrollado junto al core, renderiza componentes como objetos JavaScript sin depender de entornos nativos.

Las pruebas de componentes son solo pruebas JavaScript ejecutadas en entorno Node.js. No consideran código iOS, Android u otras plataformas que respaldan los componentes React Native. Por lo tanto, no garantizan al 100% que todo funcione para el usuario. Si hay un bug en el código iOS o Android, no lo detectarán.

Probando Interacciones de Usuario

Además de renderizar UI, tus componentes manejan eventos como onChangeText en TextInput o onPress en Button. Considera este ejemplo:

tsx
function GroceryShoppingList() {
const [groceryItem, setGroceryItem] = useState('');
const [items, setItems] = useState<string[]>([]);

const addNewItemToShoppingList = useCallback(() => {
setItems([groceryItem, ...items]);
setGroceryItem('');
}, [groceryItem, items]);

return (
<>
<TextInput
value={groceryItem}
placeholder="Enter grocery item"
onChangeText={text => setGroceryItem(text)}
/>
<Button
title="Add the item to list"
onPress={addNewItemToShoppingList}
/>
{items.map(item => (
<Text key={item}>{item}</Text>
))}
</>
);
}

Al probar interacciones, evalúa el componente desde la perspectiva del usuario: ¿qué hay en pantalla? ¿Qué cambia al interactuar?

Como regla general, usa elementos que los usuarios puedan percibir visual o auditivamente:

Por el contrario, deberías evitar:

  • hacer afirmaciones sobre props o estado de componentes

  • consultas con testID

Evita probar detalles de implementación como props o estado: aunque estas pruebas funcionan, no están orientadas a cómo los usuarios interactuarán con el componente y tienden a romperse durante refactorizaciones (por ejemplo al renombrar elementos o reescribir componentes de clase usando hooks).

Los componentes de clase React son especialmente propensos a probar detalles de implementación como estado interno, props o manejadores de eventos. Para evitar esto, prefiere componentes funcionales con Hooks, que hacen más difícil depender de internos del componente.

Bibliotecas para pruebas de componentes como React Native Testing Library facilitan escribir pruebas centradas en el usuario mediante una selección cuidadosa de sus APIs. Este ejemplo usa los métodos changeText y press de fireEvent que simulan interacción del usuario, y la función de consulta getAllByText que encuentra nodos Text coincidentes en el renderizado.

tsx
test('given empty GroceryShoppingList, user can add an item to it', () => {
const {getByPlaceholderText, getByText, getAllByText} = render(
<GroceryShoppingList />,
);

fireEvent.changeText(
getByPlaceholderText('Enter grocery item'),
'banana',
);
fireEvent.press(getByText('Add the item to list'));

const bananaElements = getAllByText('banana');
expect(bananaElements).toHaveLength(1); // expect 'banana' to be on the list
});

¡Este ejemplo no prueba cómo cambia un estado al llamar una función. Prueba qué ocurre cuando un usuario cambia texto en el TextInput y presiona el Button!

Probando el resultado renderizado

Las pruebas de instantánea son un tipo avanzado de pruebas habilitado por Jest. Es una herramienta muy potente pero de bajo nivel, por lo que se recomienda usarla con especial atención.

Una "instantánea de componente" es una cadena tipo JSX creada por un serializador personalizado de React integrado en Jest. Este serializador permite a Jest traducir árboles de componentes React a cadenas legibles por humanos. En otras palabras: una instantánea de componente es una representación textual del resultado renderizado de tu componente generado durante una prueba. Puede verse así:

tsx
<Text
style={
Object {
"fontSize": 20,
"textAlign": "center",
}
}>
Welcome to React Native!
</Text>

En pruebas de instantáneas, típicamente primero implementas tu componente y luego ejecutas la prueba. Esta crea una instantánea y la guarda como referencia en un archivo de tu repositorio. El archivo se confirma y revisa durante el code review. Cualquier cambio futuro en el renderizado alterará la instantánea, haciendo fallar la prueba. Entonces debes actualizar la instantánea de referencia para que la prueba pase. Ese cambio también debe confirmarse y revisarse.

Las instantáneas tienen varios puntos débiles:

  • Para desarrolladores o revisores, puede ser difícil determinar si un cambio en la instantánea es intencional o evidencia de un error. Las instantáneas grandes rápidamente se vuelven difíciles de entender y su valor añadido disminuye.

  • Cuando se crea una instantánea, en ese momento se considera correcta, incluso si el resultado renderizado es erróneo.

  • Cuando falla una instantánea, es tentador actualizarla con la opción de Jest --updateSnapshot sin investigar si el cambio es esperado. Se requiere cierta disciplina del desarrollador.

Las instantáneas por sí solas no garantizan que la lógica de renderizado sea correcta; solo protegen contra cambios inesperados y verifican que los componentes en el árbol de React reciban los props esperados (estilos, etc.).

Recomendamos usar solo instantáneas pequeñas (ver regla no-large-snapshots). Para probar diferencias entre estados de componentes React, usa snapshot-diff. En caso de duda, prefiere expectativas explícitas como se describe en el párrafo anterior.

Pruebas de extremo a extremo

En las pruebas de extremo a extremo (E2E), verificas que tu aplicación funciona como se espera en un dispositivo (o simulador/emulador) desde la perspectiva del usuario.

Esto se logra construyendo tu aplicación en configuración de release y ejecutando las pruebas sobre ella. En pruebas E2E, ya no piensas en componentes de React, APIs de React Native, almacenes de Redux o cualquier lógica de negocio. Ese no es el propósito de las pruebas E2E y ni siquiera son accesibles durante las pruebas E2E.

En cambio, las bibliotecas de pruebas E2E te permiten encontrar y controlar elementos en la pantalla de tu aplicación: por ejemplo, puedes realmente pulsar botones o insertar texto en TextInputs igual que lo haría un usuario real. Luego puedes verificar si cierto elemento existe en la pantalla de la app, si es visible, qué texto contiene, etc.

Las pruebas E2E te brindan el mayor nivel de confianza posible sobre el funcionamiento de partes de tu aplicación. Las contrapartidas incluyen:

  • escribirlas requiere más tiempo comparado con otros tipos de pruebas

  • son más lentas de ejecutar

  • son más propensas a ser inestables (una prueba "inestable" es aquella que a veces pasa y a veces falla sin cambios en el código)

Intenta cubrir las partes vitales de tu app con pruebas E2E: flujos de autenticación, funcionalidades clave, pagos, etc. Usa pruebas JS más rápidas para las partes no críticas. Cuantas más pruebas añadas, mayor será tu confianza, pero también dedicarás más tiempo a mantenerlas y ejecutarlas. Evalúa las compensaciones y decide qué es mejor para ti.

Existen varias herramientas de pruebas E2E disponibles: en la comunidad React Native, Detox es un framework popular por estar adaptado a apps React Native. Otra biblioteca común para apps iOS y Android es Appium o Maestro.

Resumen

Esperamos que hayas disfrutado esta guía y aprendido algo útil. Existen múltiples formas de probar tus aplicaciones. Al principio puede ser difícil decidir qué usar. Sin embargo, creemos que todo cobrará sentido cuando empieces a añadir pruebas a tu increíble app React Native. ¿Qué esperas? ¡Aumenta tu cobertura!

Enlaces


Esta guía fue escrita y contribuida completamente por Vojtech Novak.