Testar é um negócio complicado. Testar aplicativos de pilha completa é ainda mais complicado. Você tem que lidar com frontend, backend, banco de dados, rede e muito mais. Primeiro, é claro, você teste seus componentes, funções e módulos isoladamente. Então você escreve testes de integração para garantir que eles jogam bem juntos. Você pode até adicionar alguns testes de end-to-end para todo o aplicativo para simular interações reais do usuário. Mas então há o fator caos: o que acontece quando as coisas dão errado? O que acontece quando a rede é lenta ou pouco confiável? O que acontece quando o backend está abaixo? Um aplicativo que funciona perfeitamente no caminho feliz ainda pode facilmente quebrar quando algo inesperado acontece. É impossível prever todas as maneiras pelas quais um sistema pode falhar, especialmente quando múltiplos componentes interagem de maneiras complexas, mas podemos nos preparar para o fracasso testando como nosso aplicativo se comporta sob condições adversas. O teste impulsionado pelo caos é uma abordagem que abraça essa incerteza introduzindo intencionalmente falhas em seus testes. Neste artigo, vamos explorar como implementar testes impulsionados pelo caos em um aplicativo Next.js usando testes de integração que deliberadamente quebram as coisas. A aplicação Bem, primeiro precisaremos de um aplicativo para testar.Para manter o artigo focado, criei um aplicativo Next.js completo mínimo para que você não precise. O aplicativo é um aplicativo de receita simples onde você pode navegar por uma lista de receitas, visualizar detalhes de receita e como eles. Quando você gosta de uma receita, a contagem semelhante atualiza de forma otimista no frontend, enquanto o backend processa a solicitação. Se a chamada de backend falhar, a contagem semelhante retorna ao seu estado anterior. Se for bem-sucedida, retorna a nova contagem semelhante, mas o frontend não a atualiza novamente para evitar saltar números se a receita foi gostada por outro usuário. O código está disponível em . GitHub Verifique o repo, instale dependências e você pode executá-lo localmente com: git clone cd article-chaos-fetch npm install npm run dev Aberto em seu browser. http://localhost:3000 Você verá algo assim: O backend tem três rotas de API: GET /api/posts — listar todas as receitas GET /api/posts/[id] — obtenha detalhes da receita POST /api/posts/[id]/like — increment likes - retorna o novo like count Note que os likes são armazenados na memória e redefinidos no reinicio do servidor.Isso é apenas para fins de demonstração; em um aplicativo real, você usaria um banco de dados. O botão é utilizado em Usando o React e Ele lida com a atualização otimista, o gerenciamento de erros e a lógica de reversão. Provavelmente não como você ou eu a implementaria em um aplicativo real, mas faria para esta demonstração. src/components/LikeButton.tsx useState useEffect Testes de unidade com Mock Service Worker (MSW) Se você quiser testar seu componente, existem várias maneiras de fazer isso, mas uma das maneiras mais inteligentes é usar Dessa forma, você pode testar o componente isoladamente sem depender do backend real. Mock Service Worker (Trabalhador de Serviço Mock) Em O Branco, nós criamos Como o corredor de testes e Para renderizar o componente e simular as interações do usuário.Também configuramos o MSW para interceptar as solicitações da rede e retornar respostas de mock. main Rápido React Biblioteca de Testes contém os testes de unidade para a componente. Ele testa os seguintes cenários: src/components/LikeButton.test.tsx LikeButton O botão semelhante desativa durante a solicitação e atualiza para o valor de backend no sucesso. O botão semelhante desativa durante a solicitação e volta ao erro de backend. Você pode fazer os testes com: npm run test Se olharmos para o código de teste, podemos ver como usamos o MSW para ridicularizar as respostas de backend. Por exemplo, no primeiro teste, superamos a ridicularização para devolver uma resposta bem-sucedida com uma nova contagem 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 }; }) ); ... No segundo teste, substituímos o mock para devolver uma resposta de erro: 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 }; }) ); ... O que o MSW faz é interceptar as solicitações de rede feitas pelo Componente e retorna as respostas mock que definimos nos testes. Desta forma, podemos testar como o componente se comporta em diferentes condições de backend sem depender do backend real. No entanto, nenhum backend real está envolvido, então não podemos testar a integração completa entre o frontend e o backend! LikeButton Teste de integração Então, o que podemos fazer para testar a integração completa entre o frontend e o backend? Tecnicamente, poderíamos mudar o MSW para redirecionar as solicitações para o backend real, mas isso seria um pouco hacky e não realmente o que o MSW é projetado para. ou para executar testes de ponta a ponta e usar um proxy independente como Ou algo mais simples como para simular condições de rede - mas isso seria um pouco overkill para este aplicativo simples. Playwright Chipre Toxicidade Atividade Proxy Aqui é onde É uma biblioteca leve que envolve o nativo API e permite que você introduza o caos em suas solicitações de rede. Você pode simular latência, erros, limitação de taxa, throttling e até mesmo falhas aleatórias com apenas algumas linhas de código. Caos-Fetch fetch Vamos usar Se quisermos testar a integração completa entre o frontend e o backend, precisamos executar os testes em um ambiente de navegador real. ambiente para isso, que simula um ambiente semelhante ao navegador no Node.js. chaos-fetch jsdom To use `chaos-fetch`, we first need to install it: ```bash npm install @fetchkit/chaos-fetch --save-dev A primeira coisa que podemos fazer, para fins de ilustração, é trocar MSW com em nossos testes de unidade. não é realmente o que a biblioteca é projetada para, mas funciona. , substituímos a configuração do 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 você pode ver, criamos uma cliente que, em vez de recolher, devolve alguns dados de mock e substitui o A função é a mesma.Na Hok, nós restauramos o original O restante do código de teste permanece o mesmo. chaos-fetch fetch afterEach fetch Observação, também adicionamos alguma latência para simular uma solicitação de rede real. Componente desativa o botão durante a solicitação, e queremos testar esse comportamento. sem ele, o teste falharia porque a solicitação seria concluída muito rapidamente. (E isso nos leva ao mundo frágil, não confiável de testes baseados no tempo, mas isso é um tópico para outro artigo.) LikeButton O código para o segundo teste é semelhante; apenas alteramos o mock para retornar um erro: 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); ... O código está no Redação de Redação de Redação. você pode verificar com . tests-with-chaos-fetch git checkout tests-with-chaos-fetch Agora, podemos executar os testes com: npm run test Agora, o MSW ainda é mais adequado para testes de unidade, porque é projetado para esse propósito. Para isso também. chaos-fetch Teste de integração conduzida pelo caos Se quisermos ir além dos testes de unidade e testar a integração completa entre frontend e backend, podemos usar para introduzir o caos em nossos pedidos de rede. Desta forma, podemos testar como o aplicativo se comporta em condições adversas. chaos-fetch Primeiro, para os testes de integração, temos que fazer um pequeno refactor: não podemos renderizar diretamente um componente de servidor assíncope em nossos testes. O componente que contém o e os detalhes da receita. desta forma, podemos testar nos nossos testes de integração. o código para Está em . PostPage LikeButton LikeButton PostView src/components/PostView.tsx Em nosso caso, é o aplicativo inteiro, o que torna a configuração engraçada porque vamos testar um componente contra o aplicativo do qual faz parte, mas você pode configurar testes de integração da mesma forma se o backend for um serviço separado. Os testes que fizemos em São reescritos em para testar a integração completa entre o frontend e o backend. O código é semelhante, mas em vez de ridicularizar as respostas do backend, deixamos as solicitações passar para o backend real. para introduzir erros nos pedidos. LikeButton.test.tsx PostView.integration.test.tsx chaos-fetch Um detalhe importante é que devemos definir para um objeto URL, de modo que pode resolver URLs relativas corretamente. Nesta configuração, o JSDOM com a captura nativa, não funcionaria nem mesmo para URLs relativas! Patches do JSDOM para que funcione. globalThis.location chaos-fetch chaos-fetch location Em primeiro lugar, estabelecemos : globalThis.location globalThis.location = new URL("http://localhost:3000/posts/1"); Então criamos Clientes nos testes que superam a captura nativa, e podemos injetar latência, erros e muito mais. chaos-fetch Para o primeiro teste, nem sequer precisamos modificar ; só o superamos para que funcione com URLs relativas: 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 o segundo teste, criamos um cliente que simula um erro de backend para a solicitação semelhante: 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(); }); Agora, tecnicamente, no segundo teste, não chamamos o backend, porque Intercepta a solicitação e retorna um erro, mas fornece uma interface unificada para lidar com solicitações bem-sucedidas e fracassadas. chaos-fetch Onde esta abordagem realmente brilha é quando você quer simular condições de rede mais complexas. Ou você pode tentar o que acontece se seu backend está limitando sua taxa com o do middleware. throttle rateLimit Outra coisa que é difícil de testar é se um roteador de carregamento atrasado é mostrado enquanto o pedido está em vôo. middleware para simular uma rede lenta e testar que seu estado de carregamento é mostrado corretamente. latency Se você não se importa com o determinismo, você pode até adicionar algumas falhas aleatórias para ver como seu aplicativo se comporta em condições imprevisíveis. CONCLUSÃO Os testes de caos são frequentemente associados a sistemas distribuídos em grande escala, mas são igualmente importantes para aplicações menores.Neste artigo, exploramos testes de caos leve para aplicativos de pilha completa usando testes de integração que deliberadamente quebram as coisas. para introduzir o caos em nossos pedidos de rede e testar como nosso aplicativo se comporta em condições adversas. chaos-fetch não é um substituto melhor (ou pior) para MSW ou frameworks de teste end-to-end como Playwright ou Cypress. é uma ferramenta complementar que pode ser configurado e usado ao lado deles (e Ao abraçar o caos e testar como seu aplicativo se comporta em condições de falha, você pode construir aplicativos mais resilientes e robustos. @fetchkit/chaos-fetch @fetchkit/chaos-proxy