La prueba es un negocio complicado. La prueba de aplicaciones completas es aún más complicada. Usted tiene que lidiar con el frontend, el backend, la base de datos, la red y más. En primer lugar, por supuesto, usted prueba sus componentes, funciones y módulos de forma aislada. Luego escribe pruebas de integración para asegurarse de que jueguen bien juntos. Incluso puede agregar algunas pruebas de final a final para toda la aplicación para simular interacciones reales con los usuarios. Pero entonces hay el factor caos: ¿qué sucede cuando las cosas van mal? ¿Qué sucede cuando la red es lenta o poco fiable? ¿Qué sucede cuando el backend está abajo? Una aplicación que funciona perfectamente en el camino feliz todavía puede romperse fácilmente cuando sucede algo inesperado. Es imposible predecir todas las maneras en que un sistema puede fallar, especialmente cuando múltiples componentes interactúan de maneras complejas, pero podemos prepararse para el fracaso probando cómo se comporta nuestra aplicación bajo condiciones adversas. La prueba impulsada por el caos es un enfoque que abarca esta incertidumbre introduciendo intencionalmente fallos en sus pruebas.En este artículo, exploraremos cómo implementar la prueba impulsada por el caos en una aplicación Next.js utilizando pruebas de integración que intencionalmente rompen las cosas. La App Bueno, primero necesitaremos una aplicación para probar.Para mantener el artículo enfocado, he creado una aplicación Next.js completa mínima para que no tenga que. La aplicación es una aplicación de recetas sencilla donde puedes navegar por una lista de recetas, ver detalles de recetas, y como ellos. Cuando te gusta una receta, la cuenta parecida actualiza de forma optimista en el frontend mientras que el backend procesa la solicitud. Si la llamada del backend falla, la cuenta parecida vuelve a su estado anterior. Si tiene éxito, devuelve la nueva cuenta parecida, pero el frontend no la actualiza de nuevo para evitar saltar números si la receta le gustaba a otro usuario en el transcurso. El código está disponible en . GitHub Compruebe el repo, instale dependencias y puede ejecutarlo localmente con: git clone cd article-chaos-fetch npm install npm run dev abierta en su navegador. http://localhost:3000 Verás algo así: El backend tiene tres rutas API: GET /api/posts — listar todas las recetas GET /api/posts/[id] — obtenga los detalles de la receta POST /api/posts/[id]/like — increment likes - devuelve el nuevo número de like Tenga en cuenta que los likes se almacenan en la memoria y se resetan al reiniciar el servidor. Esto es sólo para fines de demostración; en una aplicación real, usaría una base de datos. Este tipo de botón se utiliza en El uso de React y Se ocupa de la actualización optimista, el manejo de errores y la lógica de reversión. Probablemente no como usted o yo lo implementaría en una aplicación real, pero lo hará para esta demostración. src/components/LikeButton.tsx useState useEffect Unidad de pruebas con Mock Service Worker (MSW) Si desea probar su componente, hay varias maneras de hacerlo, pero una de las maneras más inteligentes es usar De esta manera, puede probar el componente en aislamiento sin depender del backend real. Trabajador de Servicios Mock (MSW) En la Branch, hemos establecido Como corredor de pruebas y También configuramos MSW para interceptar las solicitudes de red y devolver respuestas de mock. main Rápido React Biblioteca de pruebas contiene las pruebas de la unidad para la componente. prueba los siguientes escenarios: src/components/LikeButton.test.tsx LikeButton El botón similar se deshabilita durante la solicitud y actualiza al valor de backend en el éxito. El botón similar se desactiva durante la solicitud y vuelve al error de backend. Puedes realizar las pruebas con: npm run test Si echamos un vistazo al código de prueba, podemos ver cómo usamos MSW para burlarnos de las respuestas de backend. Por ejemplo, en la primera prueba, superamos la burla para devolver una respuesta exitosa con un nuevo número de 42: // test("like button disables during request and updates to backend value" ... server.use( http.post("/api/posts/:id/like", async () => { await new Promise((resolve) => setTimeout(resolve, 100)); return { likes: 42 }; }) ); ... En la segunda prueba, superamos el mock para devolver una respuesta de error: test("like button disables during request and rolls back on backend error" ... server.use( http.post("/api/posts/:id/like", async () => { await new Promise((resolve) => setTimeout(resolve, 100)); return { status: 500 }; }) ); ... Lo que MSW hace es interceptar las solicitudes de red realizadas por el componente y devuelve las respuestas de mock que definimos en las pruebas. De esta manera, podemos probar cómo el componente se comporta en diferentes condiciones de backend sin depender del backend real. Sin embargo, no hay ningún backend real involucrado, por lo que no podemos probar la integración completa entre el frontend y el backend! LikeButton Pruebas de integración Por lo tanto, ¿qué podemos hacer para probar la integración completa entre el frontend y el backend?Técnicamente, podríamos cambiar MSW para reenviar las solicitudes al backend real, pero eso sería un poco hackeado y no realmente lo que MSW está diseñado para. o para ejecutar pruebas de extremo a extremo y utilizar un proxy independiente como O algo más sencillo como para simular las condiciones de red - pero eso sería un poco overkill para esta aplicación simple. El playwright Chipre Toxicidad Proyecto Proxy Aquí es donde Es una biblioteca ligera que envuelve al nativo API y le permite introducir el caos en sus solicitudes de red. Puede simular latencia, errores, limitación de tasa, throttling e incluso fallos aleatorios con solo unas pocas líneas de código. El caos-fetch fetch Vamos a utilizar Si queremos probar la integración completa entre el frontend y el backend, necesitamos ejecutar las pruebas en un entorno de navegador real. entorno para esto, que simula un entorno similar al navegador en Node.js. chaos-fetch jsdom To use `chaos-fetch`, we first need to install it: ```bash npm install @fetchkit/chaos-fetch --save-dev Lo primero que podemos hacer, para fines de ilustración, es intercambiar MSW con No es realmente lo que la biblioteca está diseñada para, pero funciona. , sustituimos la configuración de MSW por : chaos-fetch LikeButton.test,tsx chaos-fetch // src/components/LikeButton.test.tsx import { createClient, replaceGlobalFetch, restoreGlobalFetch, } from "@fetchkit/chaos-fetch"; ... describe("LikeButton", () => { afterEach(() => { restoreGlobalFetch(); }); test("like button disables during request and updates to backend value", async () => { // Mock fetch to return success const client = createClient( { global: [ { latency: { ms: 300 } }, ], routes: { "POST /api/posts/:id/like": [ { latency: { ms: 300 } }, { mock: { body: '{ "likes": 43 }' } }, ], }, }, window.fetch ); // Replace global fetch with mock client replaceGlobalFetch(client); // From here on, the test code remains the same ... Como podéis ver, hemos creado un cliente que, en lugar de recoger, devuelve algunos datos de mock y reemplaza el funcionamiento de la misma. en el Hook, vamos a restaurar el original El resto del código de prueba permanece el mismo. chaos-fetch fetch afterEach fetch Nota: también hemos añadido alguna latencia para simular una solicitud de red real. El componente deshabilita el botón durante la solicitud, y queremos probar ese comportamiento. Sin él, la prueba fracasaría porque la solicitud se completaría demasiado rápido. (Y esto nos lleva al frágil, poco fiable mundo de las pruebas basadas en el tiempo, pero eso es un tema para otro artículo.) LikeButton El código para la segunda prueba es similar; simplemente cambiamos el mock para devolver un error: test("like button disables during request and rolls back on backend error", async () => { // Mock fetch to return success const client = createClient( { global: [], routes: { "POST /api/posts/:id/like": [ { latency: { ms: 300 } }, { mock: { status: 500, body: '{ "error": "Internal Server Error" }' } }, ], }, }, window.fetch ); // Replace global fetch with mock client replaceGlobalFetch(client); ... El código está en la de la rama del repo. Puedes comprobarlo con . tests-with-chaos-fetch git checkout tests-with-chaos-fetch Ahora podemos realizar las pruebas con: npm run test Ahora, MSW es todavía un mejor apto para las pruebas de unidades, porque está diseñado para ese propósito. Por eso también. chaos-fetch Test de integración impulsada por el caos Si queremos ir más allá de las pruebas de unidades y probar la integración completa entre el frontend y el backend, podemos usar para introducir el caos en nuestras solicitudes de red. De esta manera, podemos probar cómo se comporta la aplicación en condiciones adversas. chaos-fetch En primer lugar, para las pruebas de integración, tenemos que hacer un pequeño refactor: no podemos renderizar directamente un componente de servidor asíncope en nuestras pruebas. El componente que contiene el y la receta detallada. de esta manera, podemos probar en nuestra integración. el código para Está en . PostPage LikeButton LikeButton PostView src/components/PostView.tsx En nuestro caso, es la aplicación entera, lo que hace la configuración divertida porque vamos a probar un componente contra la aplicación de la que es parte, pero puede configurar las pruebas de integración de la misma manera si el backend es un servicio separado. Las mismas pruebas que tuvimos en están reescritas en para probar la integración completa entre el frontend y el backend. El código es similar, pero en lugar de burlarse de las respuestas del backend, dejamos que las solicitudes pasen al backend real. introducir errores en las solicitudes. LikeButton.test.tsx PostView.integration.test.tsx chaos-fetch Un detalle importante es que tenemos que de un objeto URL, de modo que puede resolver correctamente las URLs relativas. En esta configuración, JSDOM con la captura nativa, ni siquiera funcionaría para las URLs relativas! Patches de JSDOM para hacer que funcione. globalThis.location chaos-fetch chaos-fetch location En primer lugar, hemos establecido : globalThis.location globalThis.location = new URL("http://localhost:3000/posts/1"); Entonces creamos clientes en las pruebas que superan la recogida nativa, y podemos inyectar latencia, errores y más. chaos-fetch Para la primera prueba, ni siquiera tenemos que modificar ; sólo lo superamos para que funcione con URLs relativos: fetch test("integration: like button disables during request and re-enables after fetch (real backend)", async () => { replaceGlobalFetch(createClient({})); render(<PostView postId={1} />); // Wait for post to load (like count should be present) const likeCountText = await screen.findByText(/\d+\s*likes/); const initialCount = Number(likeCountText.textContent.match(/(\d+)/)?.[1] ?? 0); const button = await screen.findByRole("button", { name: /like/i }); const user = userEvent.setup(); await user.click(button); // Button should be disabled during request expect(button).toBeDisabled(); // Wait for fetch to complete and UI to update await waitFor(() => expect(button).not.toBeDisabled()); // Check updated like count await waitFor(() => { const updatedLikeCountText = screen.getByText(/\d+\s*likes/); const updatedCount = Number(updatedLikeCountText.textContent.match(/(\d+)/)?.[1] ?? 0); expect(updatedCount).toBe(initialCount + 1); }); restoreGlobalFetch(); }); Para la segunda prueba, creamos un cliente que simula un error de backend para la solicitud similar: test("integration: like button disables during request and rolls back on backend error (fail middleware)", async () => { // Configure chaos-fetch to fail the like endpoint replaceGlobalFetch(createClient({ routes: { "POST /api/posts/:id/like": [ { latency: { ms: 300 } }, { fail: { status: 500, body: '{ "error": "fail middleware" }' } }, ], }, })); render(<PostView postId={1} />); // Wait for post to load (like count should be present) const likeCountText = await screen.findByText(/\d+\s*likes/); const initialCount = Number(likeCountText.textContent.match(/(\d+)/)?.[1] ?? 0); const button = await screen.findByRole("button", { name: /like/i }); const user = userEvent.setup(); await user.click(button); // Button should be disabled during request expect(button).toBeDisabled(); // Wait for fetch to complete and UI to update await waitFor(() => expect(button).not.toBeDisabled()); // Check that like count rolls back to original value await waitFor(() => { const rolledBackLikeCountText = screen.getByText(/\d+\s*likes/); const rolledBackCount = Number(rolledBackLikeCountText.textContent.match(/(\d+)/)?.[1] ?? 0); expect(rolledBackCount).toBe(initialCount); }); restoreGlobalFetch(); }); Ahora, técnicamente, en la segunda prueba, no llamamos al backend en absoluto porque Intercepta la solicitud y devuelve un error, pero proporciona una interfaz unificada para manejar solicitudes exitosas y fallidas. chaos-fetch Cuando este enfoque realmente brilla es cuando desea simular condiciones de red más complejas. Por ejemplo, puede simular redes lentas con el o puede probar lo que sucede si su backend está limitándote con el El middleware. throttle rateLimit Otra cosa que es difícil de probar es si se muestra un girador de carga retrasado mientras la solicitud está en vuelo. middleware para simular una red lenta y probar que su estado de carga se muestra correctamente. latency Si no te importa el determinismo, incluso puedes añadir algunos fallos aleatorios para ver cómo se comporta tu aplicación en condiciones impredecibles. Conclusión Las pruebas de caos a menudo se asocian con sistemas distribuidos a gran escala, pero son igualmente importantes para aplicaciones más pequeñas.En este artículo, exploramos las pruebas de caos ligeras para aplicaciones de pilas completas utilizando pruebas de integración que intencionalmente rompen las cosas. introducir el caos en nuestras solicitudes de red y probar cómo se comporta nuestra aplicación en condiciones adversas. chaos-fetch no es un mejor (o peor) reemplazo para MSW o frameworks de pruebas end-to-end como Playwright o Cypress. Es una herramienta complementaria que se puede configurar y usar junto a ellos (y Al abrazar el caos y probar cómo se comporta tu aplicación en condiciones de fracaso, puedes construir aplicaciones más resilientes y robustas. @fetchkit/chaos-fetch @fetchkit/chaos-proxy