Más allá de React Testing Library: Probando bibliotecas de React (y código similar a bibliotecas)

This ad is not shown to multipass and full ticket holders
React Summit US
React Summit US 2025
November 18 - 21, 2025
New York, US & Online
The biggest React conference in the US
Learn More
In partnership with Focus Reactive
Upcoming event
React Summit US 2025
React Summit US 2025
November 18 - 21, 2025. New York, US & Online
Learn more
Bookmark
Rate this content

Cuando se trata de probar el código de la biblioteca, el enfoque (¡generalmente asombroso!) de "Testing Library" rápidamente alcanza sus limitaciones: A menudo necesitamos probar rutas de código críticas para garantizar garantías adicionales, como un orden específico de cambios en el DOM o un número particular de renders.
Tan pronto como comenzamos a agregar Suspense a la imagen, incluso se vuelve casi filosófico:
¿Cómo contamos un render que se suspendió inmediatamente y cómo lo distinguimos de un render "comprometido"?
¿Cómo sabemos qué partes del árbol de Componentes se volvieron a renderizar?
En la base de código de Apollo Client, estamos utilizando el React Profiler para crear un flujo de eventos de render, lo que nos permite cambiar a un nuevo método de prueba basado en flujos.
Después de probar este enfoque internamente durante un año, lo hemos lanzado en una biblioteca que queremos presentar al mundo.
También miraré brevemente otros problemas "relacionados con las pruebas" muy fuera de lo común que hemos encontrado y compartiré nuestras soluciones:
Cómo probar bibliotecas que agrupan diferentes códigos para React Server Components, ejecuciones de SSR en streaming y el Navegador: Probando tus campos de `exports` cada vez más complejos y asegurando que todos esos entornos exporten la forma del paquete que esperas.
Incluso miraremos brevemente la renderización de componentes de React en diferentes entornos para probarlos de forma aislada, ya sea en Server Components, SSR en streaming o simulando la hidratación de flujos en el Navegador.

This talk has been presented at React Day Berlin 2024, check out the latest edition of this React Conference.

Lenz Weber-Tronic
Lenz Weber-Tronic
22 min
16 Dec, 2024

Comments

Sign in or register to post your comment.
Video Summary and Transcription
Mi charla se llama Más allá de Testing Library, Probando bibliotecas de React o código similar a bibliotecas. Queremos optimizar el código minimizando los re-renders, evitando el tearing y asegurando una renderización granular. React Testing Library puede no ser siempre la herramienta adecuada para bibliotecas o código similar a bibliotecas. Probamos para obtener resultados sincrónicos, pero hay casos donde pueden ocurrir re-renders no deseados e inconsistencias. Necesitamos evitar pruebas inestables y la propagación de errores. La nueva biblioteca Testing Library React Render Stream simplifica las pruebas al reemplazar envoltorios y afirmaciones complejas. Probamos múltiples componentes independientes y aseguramos la re-renderización correcta. Introducimos Suspense y la captura de instantáneas del DOM para probar la renderización granular. La prueba final proporciona una mayor confianza y cumple con todos los requisitos especiales.

1. Introduction to Beyond Testing Library

Short description:

Mi charla se llama Beyond Testing Library, Testing React Libraries or Library-like Code. Quiero optimizar el código minimizando los re-renders, evitando el tearing y asegurando un renderizado granular. React Testing Library puede no ser siempre la herramienta adecuada para bibliotecas o código similar a bibliotecas. Para dicho código, necesitamos considerar requisitos especiales. Echemos un vistazo a probar el hook useQuery en Apollo Client, articleQuery o reactQuery usando React Testing Library.

Hola a todos. Mi nombre es Lenz. Mi charla se llama Beyond Testing Library, Testing React Libraries or Library-like Code. Una breve palabra sobre mí. Mi nombre es Lenz Wiebertronik. Trabajo como Ingeniero de Software Senior en Apollo GraphQL. Y allí mantengo el cliente Apollo para la web. Pero en mi tiempo libre, también mantengo Redax Toolkit. Soy el autor de RTK Query. Y debido a mi TDAH, en realidad mantengo un montón de bibliotecas más pequeñas también. Puedes encontrarme en GitHub como FryNias, en Twitter como Fry, y no en la diapositiva, pero como Fry.dev en BlueSky.

En general, ¿por qué estamos aquí? Es un poco difícil porque me encanta testing library. Creo que es increíble. Pero no siempre es la herramienta adecuada para mí como autor de bibliotecas porque React Testing Library prueba para consistencia eventual. Y en testing library, puede que no siempre quiera buscar eso porque las bibliotecas son un camino de código caliente y necesitamos optimizar para eso. Así que para las bibliotecas que escribo y tal vez también las bibliotecas que escribes, ya sea de código abierto o como una biblioteca interna compartida por múltiples equipos, o simplemente código similar a bibliotecas, podríamos tener algunos requisitos especiales que no son algo que normalmente probarías con React Testing Library. Entonces, ¿cuáles son estos? Bueno, primero, quiero asegurarme de que mi código no cause más re-renders de los absolutamente necesarios porque este es como el código que está en medio de todo. Quieres tener eso optimizado antes de comenzar a optimizar tu propio código. Así que hacemos nuestro mejor esfuerzo aquí.

Más allá del tema de los re-renders, otra cosa importante es el tearing. Eso significa que no queremos mezclar datos del presente con datos que podrían estar en la pantalla en el futuro o en el pasado. Así que dentro de un hook, eso podría significar que devolvemos un estado inconsistente. Dentro de un componente significaría que dos hooks podrían devolver un estado inconsistente entre sí. Y en toda tu aplicación, podría significar que un componente aquí muestra estado del futuro mientras que otro componente aquí abajo muestra estado del pasado. Y luego está la tercera cosa que quiero optimizar, que es el renderizado extremadamente granular. Solo quiero que el componente que es absolutamente necesario vuelva a renderizarse, y no sus padres o sus abuelos. Dicho esto, si tuviera un hook como useQuery en Apollo Client, articleQuery o reactQuery, ¿cómo lo probaría? Veamos un ejemplo con React Testing Library primero, y creo que esta es una prueba muy común, creo. Aquí, comenzaríamos a renderizar nuestro hook useQuery, y luego comenzaríamos a hacer afirmaciones sobre eso. Así que primero probaríamos este caso a la izquierda, loading debería ser true y data debería ser undefined. Y luego probamos este caso a la derecha, donde loading es false, pero data es hello world.

2. Testing for Synchronous Results

Short description:

Probamos cosas sincrónicas, pero hay casos donde otros factores podrían causar resultados positivos que queremos evitar. Por ejemplo, las llamadas a setState o usingExternalStore pueden causar re-renders no deseados. También hay casos de tearing donde loading es true pero data ya tiene el resultado final. Estas inconsistencias pueden llevar a que se muestren valores incorrectos.

La forma en que hacemos eso aquí es que probamos las cosas que se pueden probar sincrónicamente, y luego esperamos hasta que loading sea false, y luego probamos que data sea igual a hello world. Pero, por supuesto, este es el camino feliz, esta prueba siempre será positiva, pero otras cosas también podrían ser positivas y podríamos querer evitarlas. Así que veamos este caso, y ese es un caso muy común donde podríamos tener otra llamada a setState dentro de nuestro hook que realmente no se relaciona con la salida del hook, pero causa un re-render. Otro ejemplo sería una llamada a usingExternalStore que hace lo mismo. Queremos evitar eso, pero con este tipo de prueba, realmente no tenemos forma de determinar si fue el caso. Algo más sería un caso de tearing donde loading aún sería true, pero data también ya alcanzaría el resultado final. Así que aquí tenemos un valor de retorno inconsistente, y la forma en que probamos eso, simplemente se mantiene positivo porque solo probamos que loading sea false, y mientras tanto, data podría tomar cualquier valor. Eso podría incluso ir más allá, y loading también podría tomar un valor diferente. Así que en este caso, solo un montón de cachorros. Y data podría tener un valor completamente no relacionado, y no podríamos detectar eso con esta prueba o con la mayoría de las otras pruebas también.

3. Avoiding Flaky Tests and Bug Propagation

Short description:

Podemos encontrar una condición de carrera donde loading es false y data es hello world, pero hay un pequeño tick en medio que podría desencadenar un rerender. Esto puede llevar a pruebas inestables, que queremos evitar. Aunque este comportamiento es aceptable en una aplicación normal, se vuelve problemático en bibliotecas o código reutilizado extensamente. Hemos probado diferentes enfoques de prueba, pero el componente profile ha demostrado ser el más efectivo. Al envolver nuestra llamada al hook en el componente profile y usar las funciones current result y onRender, podemos hacer afirmaciones y evitar errores en nuestro código.

Esto va un paso más allá, y tenemos una condición de carrera que es posible, y eso sería que loading es false, que es esto aquí, y luego data es hello world, que es esto aquí, pero entre medio data y loading cambian. Entonces, ¿por qué puede suceder esto? Y tenemos que reescribir toda la prueba un poco para ver por qué sucede esto.

Y si asignamos una variable aquí con una promesa, y la esperamos aquí abajo, de repente vemos que el await aquí ocurre después de esta prueba. Así que hay un pequeño tick en medio. Durante ese tick, React podría rerenderizar. Por supuesto, eso es poco probable. Eso necesita un tiempo muy específico. Lo he visto bastante confiablemente en localhost, pero no lo vería en CI. Así que esto terminaría siendo una prueba que la mayoría de las veces funciona, a veces no, y sería simplemente inestable. Y realmente no sabemos si es inestable por una buena razón o inestable por una mala razón. Así que queremos evitar eso.

Dicho todo esto, demos un paso atrás y asegurémonos de que todo esto está totalmente bien en una aplicación normal. Eventualmente evitaría que las cosas se bloqueen, y la interfaz de usuario llegaría allí. Tal vez mostraría un estado incorrecto por una fracción de segundo, pero probablemente más corto de lo que una persona podría parpadear. Pero si estamos escribiendo una biblioteca o simplemente código reutilizado extensamente, esto podría no estar bien ya, porque este único error se propagará a 100 lugares o, en nuestro caso, a miles de aplicaciones. Y además, añades muchas bibliotecas a tu aplicación. Así que supongamos que añades 20 bibliotecas. Cada biblioteca viene con uno o dos de esos errores. No has escrito una sola línea de tu propio código y ya tienes 30, 40 errores en tu código. Así que como autor de una biblioteca, quiero evitar eso a toda costa. Pero, ¿cómo pruebo esto ahora? Probamos muchas cosas diferentes, como contar renders durante la ejecución de la función render de un componente o hacer afirmaciones allí, todo tipo de cosas. Pero en un momento, todo se vino abajo, ya sea el cambio de React 16 a 17, ya sea la introducción de código con suspense. Realmente no se mantuvo. Así que lo único que se mantuvo que se convirtió hace aproximadamente un año fue usar el componente profile.

Así que ampliemos esto. Primero, envolvemos nuestra llamada al hook aquí en el componente profile. Y luego, dentro de nuestro hook, asignamos a un current result. Y luego, durante onRender, que es una función que se ejecutará exactamente cuando un render termine, simplemente tomamos el último current result y lo ponemos en la parte superior de un array. Y eso significa que ahora podemos esencialmente recorrer el array, y el array será tan largo como la cantidad de renders que tuvimos, y podemos hacer afirmaciones. Así que esperamos hasta tener al menos un elemento aquí.

4. Introducing Testing Library React Render Stream

Short description:

Para hacer las pruebas más legibles, la nueva biblioteca Testing Library React Render Stream simplifica el proceso al reemplazar envoltorios complejos y afirmaciones con una función de creación de flujo de renderizado. Al usar la función take render, podemos avanzar a través de cada renderizado y hacer afirmaciones sobre las instantáneas. Además, la biblioteca elimina la necesidad de set timeout y proporciona una forma más conveniente de probar hooks. Sin embargo, no es posible probar el conteo de renderizados de componentes, pero aún es posible realizar pruebas de consistencia tanto para el hook como para el componente.

Así que ese es el primer renderizado. Hacemos nuestra afirmación. Luego esperamos hasta tener un segundo elemento en el array, y podemos hacer afirmaciones sobre él. Eso es lo bueno, porque si solo hubiera un elemento, esto simplemente lanzará y esperará a que continúe repitiéndose. Al final, incluso esperamos como 100 milisegundos, y nos aseguramos de que no haya habido otro rerenderizado en ese tiempo simplemente afirmando sobre la longitud de las instantáneas de renderizado. Esto esencialmente hace lo que necesitamos, pero honestamente, se ve horrible, y no quiero escribir al menos dos pruebas con eso, y necesito escribir cientos.

Así que al final, somos autores de bibliotecas, así que sabemos cómo construir una biblioteca, ¿verdad? Aquí es donde llegamos a un pequeño problema con esta charla, porque cuando envié esta charla, la llamé Beyond Testing Library, pero la realidad es que ahora estoy introduciendo una nueva biblioteca de pruebas. Se llama Testing Library React Render Stream, y es una nueva biblioteca de pruebas basada en ese componente profiler que vimos antes, pero ocultándolo para que no nos moleste más y podamos probar rutas de código críticas de manera confiable.

Volvamos a esa prueba que teníamos antes con un profiler y veamos cómo podemos hacerla más legible. Comienza eliminando todo este extraño envoltorio, y los resultados actuales, y las instantáneas de renderizado cosa. Simplemente avanzamos. Lo reemplazamos con algo más fácil, y decimos, crear flujo de renderizado, y obtenemos de vuelta un objeto que tiene al menos una función de reemplazo de instantánea y una función de renderizado. Podemos llamar a esa función de reemplazo de instantánea en nuestro componente con el valor de retorno de use query, y luego llamamos a la función de renderizado, que es esencialmente la misma función de renderizado con algunos ajustes que ya conocemos de testing library.

Ahora tenemos estas afirmaciones, la espera para nosotros no es agradable. Trabajar con un error aquí no es agradable, así que ¿cómo podemos cambiar esto? Tomamos una función take render de nuestro flujo de renderizado, y esa take render tiene la buena cosa de que devuelve una promesa del próximo renderizado, y el próximo renderizado que sucederá o ya ha sucedido. Podemos simplemente paso a paso siempre llamar a take render, y pasaremos por todo renderizado por renderizado por renderizado. En este caso, estoy usando esta notación con bloques de préstamo, para que pueda reutilizar nombres de variables para que no tengamos instantánea uno, instantánea dos, e instantánea tres. Simplemente decimos, tomamos la primera instantánea, y hacemos una afirmación sobre ella. Tomamos la segunda instantánea, y hacemos una afirmación sobre ella. Eso nos deja con este set timeout aquí abajo, que tampoco es agradable, así que reemplacémoslo con algo más agradable. Podemos hacer expect take render, no para volver a renderizar. Esto tiene un valor predeterminado de 100 milisegundos, pero también puedes agregar una opción y configurarlo. La última cosa aquí es que react-testing-library también tiene un render hook, y tenemos este crear flujo de renderizado con una llamada de renderizado. Podemos hacer eso más simple y si usamos un render hook a un flujo de instantáneas con un hook directamente aquí y en lugar de tener que tomar renderizados y tomar la instantánea del renderizado, la instantánea es todo lo que nos interesa cuando probamos hooks. Así que aquí podemos hacer directamente take snapshot y usar eso. Y eso es realmente una prueba bastante buena. Así que miremos hacia atrás a los requisitos especiales que teníamos antes. Teníamos ese conteo de renderizados de componentes que queríamos probar, y no podemos hacer eso. No podemos probar que no ocurran más renderizados. Y para las pruebas de consistencia, podemos probar el hook y podemos probar el componente.

5. Testing for Multiple Independent Components

Short description:

Los hooks deberían devolver los mismos datos en múltiples componentes, pero podrían no tener siempre el tiempo correcto. Probamos los hooks useQuery y useFragment con diferentes limitaciones y comportamientos de renderizado. Al usar createRenderStream y mergeSnapshot, podemos afirmar sobre las instantáneas de que loading es verdadero y ambos hooks devuelven undefined, y que loading es falso con ambos hooks devolviendo hello world. Finalmente, aseguramos el re-renderizado correcto de los componentes en el momento adecuado.

Así que esos no son realmente un problema. Eso nos deja con la última prueba sobre múltiples componentes independientes. Y demos un paso atrás y veamos por qué eso es importante. Los hooks deberían devolver los mismos datos en múltiples componentes, pero podrían no tener siempre el tiempo correcto. Si miras setState, eso podría tener diferentes renderizados en todas las versiones de React. Si miras usar estado externo y setState en el mismo componente, en React 18, esos se agruparían individualmente juntos, pero aún tendrías un renderizado con todas las llamadas de setState y un renderizado con todas las llamadas de uso de componente, usando llamadas de estado externo. Así que aquí queremos tener una forma de probar si eso realmente funciona. Y ese error ha sido corregido en React 19, pero no siempre lo sabemos, y tenemos que probarlo. Así que aquí probamos useQuery y useFragment, dos hooks con diferentes limitaciones que en el pasado tenían un comportamiento de renderizado ligeramente diferente. Nuevamente, hacemos un createRenderStream, y esta vez usamos una instantánea inicial para dar a todo un poco de forma. Y decimos que nuestra instantánea debería contener un resultado de consulta, y debería contener un resultado de fragmento. Y en lugar de usar la función replaceSnapshot, usamos la función mergeSnapshot. Luego escribimos dos componentes. Uno de ellos llama a mergeSnapshot con el resultado de useQuery, y otro con el resultado de useFragment, y los renderizamos uno al lado del otro. Y luego podemos tomar nuestros renderizados y hacer afirmaciones sobre las instantáneas. Así que loading es verdadero, ambos hooks devolvieron undefined. Loading es falso, ambos hooks devolvieron hello world. Con esto, aseguramos que no ha habido un tercer renderizado donde un hook podría devolver una cosa y otro hook podría devolver otra cosa. Y por supuesto, probamos que no habrá otro re-renderizado al final. Así que esto nos da esta tercera marca de verificación. Y eso solo nos deja con lo último de re-renderizar el componente correcto en el momento adecuado.

6. Introducing Suspense and DOM Snapshotting

Short description:

Comenzamos con una aplicación que introduce Suspense para un re-renderizado granular. Probamos dos renderizados usando render to render stream, reemplazando la instantánea y afirmando sobre los datos. Para asegurar un renderizado correcto, observamos el DOM añadiendo la opción snapshot DOM a render to render stream, creando instantáneas completas del DOM para las afirmaciones. Tomamos width y DOM de take render para usar consultas como screen o utils en la React testing library.

Comenzamos con esta aplicación aquí, y esto introduce Suspense, lo que hará que React vuelva a renderizar diferentes cosas en diferentes momentos sin volver a renderizar el componente de la aplicación alrededor de todo. Así que tenemos una llamada de suspense fallback con un componente de carga. Tenemos un error boundary, y tenemos un componente. Porque las cosas se complicarán de otra manera, eliminemos ese error boundary por ahora, y comencemos con este tipo de ejemplo más simple.

Queremos probar dos renderizados, y usamos render to render stream. Lo primero que hacemos son instantáneas porque ya sabemos cómo funciona eso. Así que usamos replace snapshot aquí, y reemplazamos siempre el resultado. Y durante el primer renderizado, asumimos que la instantánea será undefined porque este componente no se habrá renderizado. En su lugar, el componente de carga se habrá renderizado. Y luego, durante el segundo renderizado, asumimos que los datos son iguales al saludo con hello.

Esto solo no nos da mucha seguridad, sin embargo, porque no sabemos si nuestro componente de carga realmente se renderizó o algo más. Así que en esta prueba, tenemos que mirar el DOM. Y lo que podemos hacer es snapshotting del DOM. Así que añadimos esta opción snapshot DOM a nuestro render to render stream. Y eso significa que podemos crear una nueva instantánea completa del DOM para cada renderizado a medida que ocurre y mirarlas más tarde y hacer afirmaciones sobre ellas. Tenga en cuenta que esto podría usar mucha más memoria, así que hágalo con moderación. Así que aquí, tomamos width y DOM de take render, y esencialmente, eso es como screen o utils. Si estuvieras usando la React testing library normal, tendrías las mismas consultas disponibles aquí.

7. Testing Granular Rendering and Final Remarks

Short description:

Probamos que get by text loading está en el documento, y en el segundo renderizado, ya no está en el documento, pero hello está en el documento. Para lograr un renderizado granular, usamos use track renders para asegurar que solo los hijos se vuelvan a renderizar. Esta prueba integral proporciona mayor confianza y cumple con todos los requisitos especiales.

Primero, probamos que get by text loading está en el documento, y en el segundo renderizado, probamos que ya no está en el documento, pero queremos que hello esté en el documento. Ambos fueron buenos, pero todavía hay algunas cosas más que quiero probar. Especialmente, quiero probar que app solo se renderiza una vez y solo los hijos se vuelven a renderizar. Entonces, renderizado granular, ¿cómo hacemos eso? Usamos use track renders. Lo añadimos a cada componente, y eso es algo que debemos tener en cuenta. Tenemos que escribir estos componentes para la prueba, y luego podemos usar estos componentes renderizados que sacamos de cada renderizado, y podemos afirmar que primero, es estrictamente igual solo renderiza app y el componente de carga, y durante el segundo renderizado, solo vuelve a renderizar el componente, y luego seguimos adelante y añadimos nuestro boundary de nuevo, y tomamos todo esto en una gran prueba, y esta prueba realmente me da mucha más confianza. Así que cumplimos con nuestro último requisito especial.

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

Pruebas de Componentes con Cypress vs Biblioteca de Pruebas de React
TestJS Summit 2023TestJS Summit 2023
25 min
Pruebas de Componentes con Cypress vs Biblioteca de Pruebas de React
The Talk discusses the differences between Cypress component testing and React Testing Library (RTL). It highlights the benefits of using Cypress Component Testing, such as easier handling of complex components and a more stable testing experience in CI. The comparison between SignOn and Jest focuses on low-level spying and mocking capabilities. The comparison between Cypress Intercept and Mock Service Worker (MSW) examines their network spy and mocking capabilities. The Talk also emphasizes the superior developer experience and observability provided by Cypress component testing compared to RTL.

Workshops on related topic

Diseñando Pruebas Efectivas con la Biblioteca de Pruebas de React
React Summit 2023React Summit 2023
151 min
Diseñando Pruebas Efectivas con la Biblioteca de Pruebas de React
Top Content
Featured Workshop
Josh Justice
Josh Justice
La Biblioteca de Pruebas de React es un gran marco para las pruebas de componentes de React porque responde muchas preguntas por ti, por lo que no necesitas preocuparte por esas preguntas. Pero eso no significa que las pruebas sean fáciles. Todavía hay muchas preguntas que tienes que resolver por ti mismo: ¿Cuántas pruebas de componentes debes escribir vs pruebas de extremo a extremo o pruebas de unidad de nivel inferior? ¿Cómo puedes probar una cierta línea de código que es difícil de probar? ¿Y qué se supone que debes hacer con esa persistente advertencia de act()?
En esta masterclass de tres horas, presentaremos la Biblioteca de Pruebas de React junto con un modelo mental de cómo pensar en el diseño de tus pruebas de componentes. Este modelo mental te ayudará a ver cómo probar cada bit de lógica, si debes o no simular dependencias, y ayudará a mejorar el diseño de tus componentes. Te irás con las herramientas, técnicas y principios que necesitas para implementar pruebas de componentes de bajo costo y alto valor.
Tabla de contenidos- Los diferentes tipos de pruebas de aplicaciones de React, y dónde encajan las pruebas de componentes- Un modelo mental para pensar en las entradas y salidas de los componentes que pruebas- Opciones para seleccionar elementos DOM para verificar e interactuar con ellos- El valor de los mocks y por qué no deben evitarse- Los desafíos con la asincronía en las pruebas de RTL y cómo manejarlos
Requisitos previos- Familiaridad con la construcción de aplicaciones con React- Experiencia básica escribiendo pruebas automatizadas con Jest u otro marco de pruebas unitarias- No necesitas ninguna experiencia con la Biblioteca de Pruebas de React- Configuración de la máquina: Node LTS, Yarn