Mocking APIs with MSW

In modern frontend development, building and testing React applications often requires reliable API responses — even before the backend is ready. Instead of relying on unstable endpoints or waiting for real data, developers can simulate server behavior directly in the browser using Mock Service Worker (MSW).

MSW is a powerful tool that intercepts HTTP requests made by your app and serves mocked responses, all while using the browser’s native Service Worker API. Unlike traditional mocking tools that rely on hardcoded stubs or test-only environments, MSW works at the network level. This means your application behaves as if it’s communicating with a real server, enabling realistic development, testing, and debugging — with zero backend dependency.

Whether you’re building features locally, running integration tests, or simulating failure scenarios, MSW empowers you to control API behavior with precision and clarity — making it an essential tool in the modern React developer’s toolkit.

📚 Learn more: Mock Service Worker – Quick Start Guide

[!TIP] Verify MSW is Running: When your app starts, look for the [MSW] Mocking enabled. message in your browser’s console. If you don’t see it, your worker might not be registered correctly.

Getting Hands-On with MSW

To demonstrate MSW in action, let’s walk through a hands-on example where we simulate a simple API for fetching user information in a React app. Instead of relying on a real backend, we’ll use MSW to intercept the /api/user request and return a mocked JSON response directly in the browser.

This approach not only allows us to start building our frontend independently, but also makes it easier to test edge cases, simulate failures, and ensure consistent development workflows.

The steps below will show you how to: - Define mock API handlers using MSW - Register the service worker for local development - Use fetch() inside a React component and receive a mocked response

Let’s start by creating the request handler for /api/user.

Step 1: Install MSW

npm install msw@latest

📁 Folder Structure (recommended)

src/
  mocks/
    handlers.ts
    browser.ts
  index.tsx

Defines a list of mock API endpoints and how to respond to them.

📄handlers.ts :

// src/mocks/handlers.ts
import { http, HttpResponse } from 'msw'

export const handlers = [
  http.get('/api/user', () => {
    return HttpResponse.json({ id: 1, name: 'Amit Verma' });
  }),
];

📌 Sets up the MSW worker in the browser and uses the handlers from 📄handlers.ts.

// src/mocks/browser.ts
import { setupWorker } from 'msw/browser'
import { handlers } from './handlers'

export const worker = setupWorker(...handlers);

worker.start({
  onUnhandledRequest: 'warn'
});

setupWorker() starts a Service Worker that listens to fetch and intercepts API calls in the browser. 🧠 This acts like your mock backend — it returns fake data instead of calling the real server.

Then in 📄index.tsx:


import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

const startApp = () => {
  const root = ReactDOM.createRoot(
    document.getElementById('root') as HTMLElement
  );
  root.render(
    <React.StrictMode>
      <App />
    </React.StrictMode>
  );
};

if (process.env.NODE_ENV === 'development') {
  import('./mocks/browser').then(({ worker }) => {
    worker.start({ onUnhandledRequest: 'warn' }).then(() => {
      startApp(); // ⏳ Only render app after MSW is ready
    });
  });
} else {
  startApp();
}

reportWebVitals();

📄App.tsx

import React from 'react';
import './App.css';
import MyComp from './MyComp';

function App() {

  return (
    <div className="App">
      <MyComp />
    </div>
  );
}

export default App;

📄MyComp.tsx :


import React, { useEffect, useState } from 'react';


type User = {
    id: number;
    name: string;
};

const MyComp = () => {
    const [user, setUser] = useState<User | null>(null);

useEffect(() => {
  fetch('/api/user')
    .then(async (response) => {
      const contentType = response.headers.get('Content-Type');
      if (contentType && contentType.includes('application/json')) {
        const data = await response.json();
        setUser(data);
      } else {
        throw new Error('Response is not JSON');
      }
    })
    .catch(error => console.error('Error fetching user:', error));
}, []);

    return (
        <div>
        {user ? (
            <div>
                <h1>User Details</h1>
                <p>ID: {user.id}</p>
                <p>Name: {user.name}</p>
                <p>Role: {user.role}</p>
            </div>

        ) : (
            <p>Loading user...</p>
        )}
        </div>
    );

};

export default MyComp;

we also need to run

npx msw init public/ --save

This command copies the actual service worker file (mockServiceWorker.js) into your public/ folder. Why It’s Critical • This file is registered by your browser via setupWorker(…) • Without it, you’ll see errors like:

[MSW] Failed to register a Service Worker
[MSW] Error: TypeError: Failed to register a ServiceWorker for scope...

🔄 Flow Summary

App → fetch('/api/user')

[MSW Service Worker]

handlers.ts → Matches rest.get('/api/user')

Returns mocked response (e.g. { id: 1, name: "Amit" })

🧪 Test It

In your React component, write:

useEffect(() => {
  fetch('/api/user')
    .then(res => res.json())
    .then(data => console.log('Mocked User:', data))
}, [])

When you run your app, it should log: Mocked User: { id: 1, name: ‘Amit Verma’, role: ‘Lead Software Engineer’ }

📄package.json

{
  "name": "my-react-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/dom": "^10.4.0",
    "@testing-library/jest-dom": "^6.6.3",
    "@testing-library/react": "^16.3.0",
    "@testing-library/user-event": "^13.5.0",
    "@types/jest": "^27.5.2",
    "@types/node": "^16.18.126",
    "@types/react": "^19.1.8",
    "@types/react-dom": "^19.1.6",
    "msw": "^2.10.4",
    "react": "^19.1.0",
    "react-dom": "^19.1.0",
    "react-scripts": "5.0.1",
    "typescript": "^4.9.5",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "msw": {
    "workerDirectory": [
      "public"
    ]
  }
}