MSW lets you test API interactions by intercepting network requests and returning controlled responses, without calling real servers.
MSW is useful for testing:
Unlike mocking fetch or Axios directly, MSW intercepts requests at the network boundary. Your application can continue using its normal API client.
Install MSW:
npm install -D msw
Create a test server:
// src/mocks/server.tsimport { http, HttpResponse } from "msw";import { setupServer } from "msw/node";export const server = setupServer(http.post("/greeting", async ({ request }) => {const { username } = (await request.json()) as {username: string;};return HttpResponse.json({message: `Hello, ${username}!`,});}));
Configure the test lifecycle:
// src/setupTests.tsimport { beforeAll, afterEach, afterAll } from "vitest";import { server } from "./mocks/server";beforeAll(() => {server.listen({ onUnhandledRequest: "error" });});afterEach(() => {server.resetHandlers();});afterAll(() => {server.close();});
This setup uses Vitest. With Jest, use Jest’s lifecycle functions instead.
import { render, screen } from "@testing-library/react";import userEvent from "@testing-library/user-event";import { expect, test } from "vitest";import Fetch from "../Fetch";test("loads and displays a greeting", async () => {const user = userEvent.setup();render(<Fetch url="/greeting" />);await user.click(screen.getByRole("button", { name: /load greeting/i }));expect(await screen.findByRole("heading", {name: "Hello, Alice!",})).toBeInTheDocument();});
This assumes the component submits { username: "Alice" } and renders the returned message as a heading. Adjust the expected text to match your component’s actual behavior.
Use server.use() to override a handler for one test:
import { http, HttpResponse } from "msw";import { server } from "../mocks/server";test("displays an error when the server fails", async () => {server.use(http.post("/greeting", () =>HttpResponse.json({ message: "Something went wrong" },{ status: 500 })));const user = userEvent.setup();render(<Fetch url="/greeting" />);await user.click(screen.getByRole("button", { name: /load greeting/i }));expect(await screen.findByRole("alert")).toHaveTextContent(/something went wrong/i);});
This assumes Fetch displays a suitable error message in an element with role="alert" when the request fails.
server.resetHandlers() restores the initial handlers after each test, preventing overrides from leaking into other tests.