By using this site, you agree to the Privacy Policy and Terms of Use.
Accept
World of SoftwareWorld of SoftwareWorld of Software
  • News
  • Software
  • Mobile
  • Computing
  • Gaming
  • Videos
  • More
    • Gadget
    • Web Stories
    • Trending
    • Press Release
Search
  • Privacy
  • Terms
  • Advertise
  • Contact
Copyright © All Rights Reserved. World of Software.
Reading: Embracing the Uncertainty of Chaos-Driven Testing: Integration Tests That Can Destroy and Rebuild | HackerNoon
Share
Sign In
Notification Show More
Font ResizerAa
World of SoftwareWorld of Software
Font ResizerAa
  • Software
  • Mobile
  • Computing
  • Gadget
  • Gaming
  • Videos
Search
  • News
  • Software
  • Mobile
  • Computing
  • Gaming
  • Videos
  • More
    • Gadget
    • Web Stories
    • Trending
    • Press Release
Have an existing account? Sign In
Follow US
  • Privacy
  • Terms
  • Advertise
  • Contact
Copyright © All Rights Reserved. World of Software.
World of Software > Computing > Embracing the Uncertainty of Chaos-Driven Testing: Integration Tests That Can Destroy and Rebuild | HackerNoon
Computing

Embracing the Uncertainty of Chaos-Driven Testing: Integration Tests That Can Destroy and Rebuild | HackerNoon

News Room
Last updated: 2025/10/10 at 12:16 AM
News Room Published 10 October 2025
Share
SHARE

Testing is a tricky business. Testing full-stack apps is even trickier. You have to deal with frontend, backend, database, network, and more. First, of course, you unit test your components, functions, and modules in isolation. Then you write integration tests to ensure they play nicely together. You might even add a few end-to-end tests for the entire application to simulate real user interactions.

But then there’s the chaos factor: what happens when things go wrong? What happens when the network is slow or unreliable? What happens when the backend is down? An application that works perfectly on the happy path can still easily break when something unexpected happens. It is impossible to predict all the ways in which a system can fail, especially when multiple components interact in complex ways, but we can prepare for failure by testing how our application behaves under adverse conditions.

Chaos-driven testing is an approach that embraces this uncertainty by intentionally introducing failures into your tests. In this article, we’ll explore how to implement chaos-driven testing in a Next.js application using integration tests that intentionally break things.

The App

Well, first we’ll need an app to test. To keep the article focused, I’ve created a minimal full-stack Next.js app so you don’t have to.

The app is a simple recipe app where you can browse a list of recipes, view recipe details, and like them. It uses Tailwind CSS for styling and TypeScript for type safety.

When you like a recipe, the like count updates optimistically on the frontend while the backend processes the request. If the backend call fails, the like count reverts to its previous state. If it succeeds, it returns the new like count, but the frontend doesn’t update it again to avoid jumping numbers if the recipe was liked by another user in the meantime. This is a common pattern in web apps to provide a snappy user experience when consistency is not critical.

The code is available on GitHub.

Check out the repo, install dependencies, and you can run it locally with:

git clone
cd article-chaos-fetch
npm install
npm run dev

Open http://localhost:3000 in your browser.

You will see something like this:

The backend has three API routes:

  • GET /api/posts — list all recipes
  • GET /api/posts/[id] — get recipe details
  • POST /api/posts/[id]/like — increment likes – returns the new like count

Note that the likes are stored in-memory and reset on server restart. This is just for demonstration purposes; in a real app, you’d use a database.

The like button is implemented in src/components/LikeButton.tsx using React’s useState and useEffect hooks. It handles the optimistic update, error handling, and reversion logic. Probably not how either you or I would implement it in a real app, but it will do for this demo.

Unit Tests with Mock Service Worker (MSW)

If you want to unit test your component, there are multiple ways to do that, but one of the smartest ways is to use Mock Service Worker (MSW) to mock the backend API calls. This way, you can test the component in isolation without relying on the actual backend.

In the main branch, we set up Vitest as the test runner and React Testing Library for rendering the component and simulating user interactions. We also set up MSW to intercept the network requests and return mock responses.

src/components/LikeButton.test.tsx contains the unit tests for the LikeButton component. It tests the following scenarios:

  • The like button disables during the request and updates to the backend value on success.
  • The like button disables during the request and rolls back on backend error.

You can run the tests with:

npm run test

If we have a look at the test code, we can see how we use MSW to mock the backend responses. For example, in the first test, we override the mock to return a successful response with a new like count of 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 };
  })
);
...

In the second test, we override the mock to return an 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 };
  })
);
...

What MSW does is intercept the network requests made by the LikeButton component and returns the mock responses we defined in the tests. This way, we can test how the component behaves under different backend conditions without relying on the actual backend. However, no real backend is involved, so we can’t test the full integration between frontend and backend!

Integration Tests

So, what can we do to test the full integration between frontend and backend? Technically, we could change MSW to forward the requests to the actual backend, but that would be a bit hacky and not really what MSW is designed for. Another viable option is to use a real browser environment like Playwright or Cypress to run end-to-end tests and use a standalone proxy like toxiproxy or something simpler like chaos-proxy to simulate network conditions – but that would be a bit overkill for this simple app.

This is where chaos-fetch comes in. It is a lightweight library that wraps the native fetch API and allows you to introduce chaos into your network requests. You can simulate latency, errors, rate limiting, throttling, and even random failures with just a few lines of code.

Let’s use chaos-fetch to create some integration tests. If we want to test the full integration between frontend and backend, we need to run the tests in a real browser environment. We can use Vitest’s jsdom environment for this, which simulates a browser-like environment in Node.js.

To use `chaos-fetch`, we first need to install it:

bash
npm install @fetchkit/chaos-fetch –save-dev


The first thing we can do, for illustration purposes, is to swap MSW with `chaos-fetch` in our unit tests. It's not really what the library is designed for, but it works. In `LikeButton.test,tsx`, we replace the MSW setup with `chaos-fetch`:

typescript
// 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
…


As you can see, we create a `chaos-fetch` client that, instead of fetching, returns some mock data and replaces the global `fetch` function with it. In the `afterEach` hook, we restore the original `fetch` function. The rest of the test code remains the same.


Note, we also added some latency to simulate a real network request. This is important because the `LikeButton` component disables the button during the request, and we want to test that behavior. Without it, the test would fail because the request would complete too quickly. (And this leads us to the brittle, unreliable world of time-based testing, but that's a topic for another article.)


The code for the second test is similar; we just change the mock to return an error:

typescript
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);
…


The code is on the `tests-with-chaos-fetch` branch of the repo. You can check it out with `git checkout tests-with-chaos-fetch`.


Now, we can run the tests with:

bash
npm run test


Now, MSW is still a better fit for unit tests, because it is designed for that purpose. But it's definitely possible to use `chaos-fetch` for that as well.

## Chaos-Driven Integration Tests

If we want to move beyond unit tests and test the full integration between frontend and backend, we can use `chaos-fetch` to introduce chaos into our network requests. This way, we can test how the application behaves under adverse conditions.


First, for the integration tests, we have to do a small refactor: we cannot directly render an async server component in our tests. Instead, we create a `PostPage` component that contains the `LikeButton` and the recipe details. This way, we can test `LikeButton` in our integration tests. The code for `PostView` is in `src/components/PostView.tsx`.


Next, we have to make sure the backend is running before we run the tests. In our case, it's the whole app, which makes the setup funny cause we will test a component against the app it is part of, but you can set up integration tests the same way if the backend is a separate service.


The same tests we had in `LikeButton.test.tsx` are rewritten in `PostView.integration.test.tsx` to test the full integration between frontend and backend. The code is similar, but instead of mocking the backend responses, we let the requests go through to the actual backend. We still use `chaos-fetch` to introduce errors into the requests.


An important detail is that we have to set `globalThis.location` to a URL object, so that `chaos-fetch` can resolve relative URLs correctly. In this setup, JSDOM with the native fetch, wouldn't even work for relative URLs! `chaos-fetch` patches JSDOM's `location` to make it work.


So first, we set `globalThis.location`:

typescript
globalThis.location = new URL(“http://localhost:3000/posts/1”);


Then we create `chaos-fetch` clients in the tests that override the native fetch, and we can inject latency, errors, and more.


For the first test, we don't even have to modify `fetch`; we only override it so it works with relative URLs:

typescript
test(“integration: like button disables during request and re-enables after fetch (real backend)”, async () => {
replaceGlobalFetch(createClient({}));

render();

// 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();
});


For the second test, we create a client that simulates a backend error for the like request:

typescript
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();

// 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();
});
“`

Now, technically, in the second test, we don’t call the backend at all because chaos-fetch intercepts the request and returns an error. But it gives a unified interface to handle successful and failing requests alike.

Where this approach really shines is when you want to simulate more complex network conditions. For example, you can simulate slow networks with the throttle middleware. Or you can try what happens if your backend is rate-limiting you with the rateLimit middleware.

Another thing that’s hard to test is whether a delayed loading spinner is shown while the request is in flight. You can use the latency middleware to simulate a slow network and test that your loading state is shown correctly.

If you don’t care about determinism, you can even add some random failures to see how your app behaves under unpredictable conditions. You can also write and register your own custom middleware to simulate specific scenarios.

Conclusion

Chaos testing is often associated with large-scale distributed systems, but it’s equally important for smaller applications. In this article, we explored lightweight chaos-driven testing for full-stack apps using integration tests that intentionally break things. We saw how to use chaos-fetch to introduce chaos into our network requests and test how our application behaves under adverse conditions.

@fetchkit/chaos-fetch is not a better (or worse) replacement for MSW or end-to-end testing frameworks like Playwright or Cypress. It is a complementary tool that can be set up and used alongside them (and @fetchkit/chaos-proxy) to enhance your testing strategy. By embracing chaos and testing how your application behaves under failure conditions, you can build more resilient and robust applications.

Sign Up For Daily Newsletter

Be keep up! Get the latest breaking news delivered straight to your inbox.
By signing up, you agree to our Terms of Use and acknowledge the data practices in our Privacy Policy. You may unsubscribe at any time.
Share This Article
Facebook Twitter Email Print
Share
What do you think?
Love0
Sad0
Happy0
Sleepy0
Angry0
Dead0
Wink0
Previous Article You can now grab the Pixel 10 Pro Fold for just $799 with this massive T-Mobile deal
Next Article 4 devices I refuse to use without an Ethernet connection
Leave a comment

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Stay Connected

248.1k Like
69.1k Follow
134k Pin
54.3k Follow

Latest News

Agibot denies backdoor listing via Swancor Advanced Materials acquisition · TechNode
Computing
Channel 4’s streamer is about to get a lot more content thanks to the BBC
Gadget
YouTube Launches ‘Second Chance’ Program for Banned Creators
News
How to Organize Your Content Calendar by Campaign |
Computing

You Might also Like

Computing

Agibot denies backdoor listing via Swancor Advanced Materials acquisition · TechNode

1 Min Read
Computing

How to Organize Your Content Calendar by Campaign |

32 Min Read
Computing

Chinese GenAI venture raises $14 million, claims itself akin to Sora · TechNode

1 Min Read
Computing

Free Blood Pressure Tracker Templates to Monitor Your Health

28 Min Read
//

World of Software is your one-stop website for the latest tech news and updates, follow us now to get the news that matters to you.

Quick Link

  • Privacy Policy
  • Terms of use
  • Advertise
  • Contact

Topics

  • Computing
  • Software
  • Press Release
  • Trending

Sign Up for Our Newsletter

Subscribe to our newsletter to get our newest articles instantly!

World of SoftwareWorld of Software
Follow US
Copyright © All Rights Reserved. World of Software.
Welcome Back!

Sign in to your account

Lost your password?