Ang pagsubok ay isang mahigpit na negosyo. Ang pagsubok ng mga full-stack apps ay mas mahigpit. Kailangan mo upang magtrabaho sa frontend, backend, database, network, at higit pa. Sinabi, unang, ikaw ay isang unit na pagsubok ng iyong mga bahagi, mga function, at mga module sa isolation. At pagkatapos ay inilagay mo ang mga pagsubok ng integration upang matugunan ang mga ito na mag-play nang magandang kasama. Maaari mo kahit na magdadala ng ilang end-to-end na pagsubok para sa buong application upang simulate ang tunay na user interactions. But then there’s the chaos factor: ano ang nangyayari kapag ang mga bagay ay bumaba? Ano ang nangyayari kapag ang network ay mas madali o hindi matatagpuan? Ano ang nangyayari kapag ang backend ay bumaba? Ang isang application na gumagana nang magandang sa happy path ay maaaring madaling mag-atake kapag ang isang bagay na hindi na-expect ay nangangailangan. Ang Chaos-driven testing ay isang paghahatid na nagpapahiwatig ng uncertainty na ito sa pamamagitan ng pag-introduce ng mga tagumpay sa iyong mga pagsubok. Sa artikulong ito, i-explore kung paano i-implementate ang chaos-driven testing sa isang Next.js application gamit ang mga pagsubok ng integration na intentionally mamatay ang mga bagay. ang app Well, first we'll need a app to test. Upang mag-focus ang artikulong, natuklasan ko ang isang minimum na full-stack Next.js app para sa iyo ay hindi ka kailangan. Ang app ay isang simpleng recipe app kung saan maaari mong i-broadcast ang isang listahan ng mga recipe, tingnan ang mga detalye ng recipe, at tulad ng mga ito. Ito ay gumagamit Tailwind CSS para sa styling at TypeScript para sa seguridad ng uri. Kapag gusto mo ng isang recipe, ang like count update optimistically sa frontend habang ang backend processes ang request. Kung ang backend call fails, ang like count ay bumalik sa kanyang mga nakaraang estado. Kung lumabas, ito ay bumalik ang bagong like count, ngunit ang frontend ay hindi update ito taas upang maiwasan ang jumping mga numero kung ang recipe ay gusto ng ibang gumagamit sa pagitan. Ito ay isang karaniwang pattern sa web apps upang magbigay ng isang snappy user experience kapag ang consistency ay hindi kritikal. Ang code ay magagamit sa . ang github Tingnan ang repo, i-install dependencies, at maaari mong i-execute ito locally sa pamamagitan ng: git clone cd article-chaos-fetch npm install npm run dev Open ang sa iyong browser. http://localhost:3000 Makikita mo ang isang bagay tulad ng ito: Ang backend ay may tatlong API routes: GET /api/posts — listahan ang lahat ng mga recipe GET /api/posts/[id] — makakuha ng mga detalye ng recipe POST /api/posts/[id]/like — increment likes - returns the new like count Tingnan na ang mga likes ay ibinigay sa memory at i-reset sa reboot ng server. Ito ay lamang para sa mga propesyonal; sa isang tunay na app, ginagamit mo ang isang database. Ang mga button na ito ay inilagay sa Tungkol sa React at ang Ito ay nagtatrabaho sa optimistic update, error handling, at reversion logic. Malamang hindi kung paano ikaw o ako ay mag-implementate ito sa isang tunay na app, ngunit ito ay gawin para sa demo na ito. src/components/LikeButton.tsx useState useEffect Mga Test ng Unit sa Mock Service Worker (MSW) Kung nais mong i-test ang iyong mga bahagi, mayroong maraming mga paraan upang gawin ito, ngunit isa sa mga pinakamahusay na paraan ay upang gamitin ang Sa pamamagitan ng ito, maaari mong i-test ang component sa isolation nang walang pag-iisip sa tunay na backend. Mga Mapagkukunan ng Mock Service Worker (MSW) sa mga Branch, kami ay nag-set up Sa pamamagitan ng test runner at Para sa pag-render ng mga bahagi at pag-simulate ng user interactions.We also set up MSW to intercept the network requests and return mock responses. main Maghanap ng Mga React Testing Library Ipinapakita ang Unit Test para sa Ang mga ito ay nagtatrabaho sa mga sumusunod na mga scenario: src/components/LikeButton.test.tsx LikeButton Ang Like button ay inactivate sa panahon ng request at update sa backend na halaga sa pagganap. Ang Like button ay inactivate sa panahon ng request at bumalik sa backend error. Maaari mong i-execute ang mga test sa: npm run test Kung makikita natin ang code ng pagsubok, maaari naming makita kung paano ginagamit namin ang MSW upang mamatay ang mga backend responses. Halimbawa, sa unang pagsubok, tumuturo namin ang mock upang ibalik ang isang sikat na reaksyon na may isang bagong bilang ng 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 }; }) ); ... Sa ikalawang test, i-override ang mock upang i-return ang isang error response: 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 }; }) ); ... Ano ang ginagawa ng MSW ay pag-intercept ang mga requests ng network na ginawa ng Pagkatapos ng pagsusuri, i-configure ang mga component at i-return ang mock responses na natuklasan namin sa mga test. Sa paraan na ito, maaari naming i-test kung paano ang component ay nagtatrabaho sa iba't-ibang mga kondisyon ng backend nang walang pag-iisip sa tunay na backend. Gayunpaman, walang tunay na backend ay sumali, kaya hindi namin maaaring i-test ang buong integration sa pagitan ng frontend at backend! LikeButton Mga integration test Kaya, ano ang maaari nating gawin upang i-test ang buong integration sa pagitan ng frontend at backend? Technically, maaari naming i-change ang MSW upang i-redirect ang mga request sa actual backend, ngunit ito ay isang bit hacking at hindi talagang kung ano ang MSW ay dinisenyo para sa. o sa upang i-execute ang end-to-end tests at gamitin ang isang standalone proxy tulad ng Sa isang simpleng bagay tulad ng upang i-simulate ang mga kondisyon ng network - ngunit ito ay isang maliit na overkill para sa simpleng app. ang playwright sa Cyprus Mga Toksiko Mga Proxy Ito ay kung saan Ito ay isang maliit na library na naglalaman ng mga tao. API at nagbibigay-daan sa iyo upang i-introduce chaos sa iyong network requests. You can simulate latency, error, rate limiting, throttling, at kahit na random failures sa lamang ng ilang mga linya ng code. Kailangan ng Chaos fetch Gawin natin ang Kung nais naming i-test ang buong integration sa pagitan ng frontend at backend, kailangan naming i-run ang mga test sa isang real browser environment. ito, na simulan ng isang browser-like environment sa Node.js. chaos-fetch jsdom To use `chaos-fetch`, we first need to install it: ```bash npm install @fetchkit/chaos-fetch --save-dev Ang unang bagay na maaari naming gawin, para sa purong ilustrasyon, ay upang i-swap ang MSW sa sa aming mga test unit. Ito ay hindi talagang kung ano ang library ay dinisenyo para sa, ngunit ito ay gumagana. I-substitute ang MSW setting sa : 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 ... Sa tingin mo, lumikha kami ng isang ang client na, sa halip ng pagkuha, i-return ang ilang mga data ng mock at i-replace ang global sa pamamagitan ng ito. sa pamamagitan ng hook, i'll restore ang original Ang rest ng test code ay parehong. chaos-fetch fetch afterEach fetch Tandaan, inilagay namin din ang ilang latency upang i-simulate ang isang real network request. Ito ay mahalaga dahil ang Ang component disables ang button sa panahon ng request, at nais naming i-test ang behavior na ito. Kung hindi ito, ang test ay hindi makakapasok dahil ang request ay magsisimula nang mas mabilis. (At ito ay nagpapahiwatig sa amin sa brittle, hindi dapat i-reliable mundo ng time-based testing, ngunit ito ay isang tema para sa isang iba pang artikulo.) LikeButton Ang code para sa ikalawang pagsusuri ay pareho; kami lamang mababago ang mock upang ibalik ang isang 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); ... Ang code ay sa Pumili ng kategorya, gaya ng mga gasolinahan o grocery store, o maghanap ng partikular na uri ng lugar. . tests-with-chaos-fetch git checkout tests-with-chaos-fetch Ngayon, maaari naming i-run ang mga test na may: npm run test Ngayon, ang MSW ay mas mahusay para sa mga test ng unit, dahil ito ay dinisenyo para sa ito. Para sa mga ito too. chaos-fetch Mga Test ng Chaos-Driven Integration Kung nais naming pumunta sa higit sa mga test ng unit at i-test ang buong integrasyon sa pagitan ng frontend at backend, maaari naming gamitin ang Upang i-introduce ang chaos sa aming network requests.Ito, maaari naming i-test kung paano ang application ay nagtatrabaho sa mga katangian. chaos-fetch Sa unang, para sa mga pagsubok ng integration, kailangan nating gawin ang isang maliit na refactor: hindi namin direktang mag-render ng isang async server component sa aming mga pagsubok. Ang component na ito ay may at ang mga detalye ng recipe. Sa paraan na ito, maaari naming i-test sa aming integration tests. Ang code para sa ay in . PostPage LikeButton LikeButton PostView src/components/PostView.tsx Sa aming kaso, ito ay ang buong app, na kung saan ang pag-setup ay masaya dahil kami ay mag-test ng isang bahagi laban sa app na ito ay bahagi ng, ngunit maaari mong i-set up integration tests sama-sama kung ang backend ay isang separang serbisyo. Ang mga test na natutunan natin sa ay re-written sa Upang i-test ang buong integration sa pagitan ng frontend at backend. Ang code ay parehong, ngunit sa halip ng pag-iisip ng mga backend responses, nagbibigay kami ng mga request upang pumunta sa actual backend. Ipasok ang mga error sa mga requests. LikeButton.test.tsx PostView.integration.test.tsx chaos-fetch Ang importante na detalye ay na kailangan nating sa pamamagitan ng isang URL, at Dapat i-resolve ang relational URLs correctly. Sa setup na ito, ang JSDOM na may native fetch, ay hindi kahit na gumagana para sa relational URLs! Mga pahinang tumuturo sa JSDOM Gawin ito sa trabaho. globalThis.location chaos-fetch chaos-fetch location Dahil dito, kami ay nag-set : globalThis.location globalThis.location = new URL("http://localhost:3000/posts/1"); Pagkatapos ay lumikha kami ng mga kliyente sa mga pagsusuri na i-override ang native catch, at maaari naming i-inject ang latency, mga error, at higit pa. chaos-fetch Para sa unang pagsusuri, hindi na kailangang mag-modify ; kami lamang override ito upang ito gumagana sa relatibong URLs: 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 sa ikalawang pagsubok, lumikha namin ang isang client na simulan ng isang backend error para sa parehong request: 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(); }); Ngayon, technically, sa ikalawang pagsubok, hindi namin tinatawag ang backend sa lahat dahil I-intercept ang request at i-return ang error, ngunit ito ay nagbibigay ng isang unified interface para sa paghahatid ng ganap na mga request at nangangahulugan. chaos-fetch Kung ang isang taong nakakaalam ng higit pa tungkol sa ito ay maaaring i-verify ito, babaguhin ko ang input domain sa ASCII. O maaari mong subukan kung ano ang nangyayari kung ang iyong backend ay rate-limiting sa iyo sa ang middleware. throttle rateLimit Ang isa pang bagay na napaka-test ay kung ang isang retarded loading spinner ay nagpapakita habang ang request ay sa flight. Middleware upang i-simulate ang isang slow network at i-test na ang iyong status ng load ay tinatawag na korektong. latency Kung hindi mo interesado tungkol sa determinismo, maaari mo kahit na magdagdag ng ilang random na mga error upang makita kung paano ang iyong app gumagana sa mga hindi mapagkahiwalay na mga kondisyon. Maaari kang mag-script at i-register ang iyong personal na middleware upang simulate ang anumang mga scenarios. Konklusyon Ang Chaos testing ay karaniwang pinagsasama sa mga malalaking distributed systems, ngunit ito ay katumbas na mahalaga para sa mas maliit na mga application. Sa artikulong ito, nag-eksplorado namin ang kahanga-hangang chaos-driven testing para sa full-stack apps gamit ang integration tests na intentionally mamatay ang mga bagay. i-introduce chaos sa aming network requests at i-test kung paano gumagana ang aming application sa mga karaniwang mga kondisyon. chaos-fetch ito ay hindi isang mas mahusay (o mas mababang) substitute para sa MSW o end-to-end testing frameworks tulad ng Playwright o Cypress. Ito ay isang complementary tool na maaaring i-set up at gamitin kasama ang mga ito (at Sa pamamagitan ng pagkuha ng chaos at pag-teste kung paano ang iyong application ay gumagana sa mga kondisyon ng pag-atake, maaari mong bumuo ng mas resilient at mas malakas na mga application. @fetchkit/chaos-fetch @fetchkit/chaos-proxy