Mock Service Worker, or MSW, lets you mock API requests at the network level so React components receive realistic responses without a real server. This guide shows how to mock API requests with MSW in a Vitest test suite. The component keeps its real fetch code while the network is controlled from the test side.
Install and write handlers
Install MSW as a development dependency.
npm install -D mswA request handler declares which request to match and what to respond. Handlers live in one file that tests and local development can share.
// src/mocks/handlers.js
import { http, HttpResponse } from "msw";
export const handlers = [
http.get("/api/user", () => {
return HttpResponse.json({ name: "Ada" });
}),
];The http namespace describes the request method and URL. HttpResponse builds the response body, so the mocked endpoint returns the same shape a real server would send. Defining handlers once and reusing them across tests keeps the mock in one place instead of scattering stubs through every file.
Start a mock server for Node
Tests run in Node, so the setupServer function from msw/node patches the fetch and http modules that the component uses.
// src/mocks/node.js
import { setupServer } from "msw/node";
import { handlers } from "./handlers";
export const server = setupServer(...handlers);The server does nothing until it is started. Patching the request layer means the component's own fetch call stays untouched, which keeps tests close to production behavior. A Vitest setup file starts it before all tests, resets handlers between tests, and closes it at the end.
// src/setupTests.js
import { beforeAll, afterEach, afterAll } from "vitest";
import { server } from "./src/mocks/node";
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());Register this file in the test setupFiles array of your Vitest config. From then on, every test shares the same mock server and starts with clean handlers. If the setup file is not registered, the server never starts and every request hits the real network.
Test a component against the mock
A profile component fetches from the mocked endpoint without knowing it is mocked.
import { useEffect, useState } from "react";
export default function Profile() {
const [name, setName] = useState(null);
useEffect(() => {
let ignore = false;
fetch("/api/user").then((r) => r.json()).then((data) => {
if (!ignore) setName(data.name);
});
return () => { ignore = true; };
}, []);
if (name === null) return <p>Loading profile</p>;
return <h1>{name}</h1>;
}The test below renders the component and waits for the name that the handler returns. Nothing about the component changes, because the mock lives at the network boundary.
import { render, screen } from "@testing-library/react";
import Profile from "./Profile";
test("shows the name returned by the API", async () => {
render(<Profile />);
expect(await screen.findByText("Ada")).toBeVisible();
});The component performs a real fetch, MSW intercepts it, and the handler responds with Ada. The test sees the final UI, which is what a user would see. For the async utilities behind the wait, review how to test async React components.
Override a response for one test
Sometimes a single test needs a different response, such as an empty list or an error. Use server.use to add a temporary handler inside that test.
import { http, HttpResponse } from "msw";
import { render, screen } from "@testing-library/react";
import { server } from "./src/mocks/node";
import UserList from "./UserList";
test("shows a message when there are no users", async () => {
server.use(
http.get("/api/users", () => HttpResponse.json([]))
);
render(<UserList />);
expect(await screen.findByText("No users yet")).toBeVisible();
});The new handler wins for that test only. resetHandlers in the afterEach hook removes it before the next test runs. This keeps each test's response explicit at the point where it matters.
Common mistakes
- Patching fetch by hand with vi.fn when MSW would keep the real request path intact.
- Forgetting resetHandlers, so one test leaks its override into the next.
- Registering the setup file but not starting the server with listen.
- Mocking the response shape differently from the real API, so tests pass against a lie.
What to learn next
MSW pairs with the fetch components covered in the async testing guide, and the runner setup lives in how to test React components with Vitest.
Rune AI
Key Insights
- Install msw and define handlers with http and HttpResponse.
- Create a server with setupServer from msw/node.
- Start, reset, and close the server in a Vitest setup file.
- Override responses per test with server.use.
- Mock the network layer so components keep their real fetch code.
Frequently Asked Questions
Does MSW replace my real backend in production?
Can I change a mock response for one test?
Conclusion
MSW mocks the network layer instead of a single function. Define handlers once, start a server for tests, and override responses per test with server.use.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.