I was building a frontend feature the other day, and I hit a familiar wall. My component needed data from an API, but the backend wasn’t ready. My tests were failing, not because my logic was wrong, but because they were trying to call endpoints that didn’t exist yet. This is a daily reality for many of us. We need to test our code in isolation, but mocking network requests often feels clunky and unrealistic. That’s when I decided to look for a better way, and I found a powerful combination: Jest and Mock Service Worker (MSW).
Let’s talk about why this pairing works so well. Traditional mocking involves replacing functions like fetch or methods on axios directly in your code. This works, but it has a significant downside. Your application code in the test environment is now different from your production code. What if the way you make a request changes? Your mocks might break, or worse, they might pass while hiding a real bug. MSW takes a different approach. Instead of changing your code, it intercepts the request after your code makes it, but before it goes over the network.
Think of it like a traffic cop for your tests. Your application sends out a request, and MSW stops it, checks its destination, and provides a predefined response. Your component or service never knows the difference. It behaves exactly as it would with a real API. This creates tests that are far more reliable and true to how your app operates for a real user. Isn’t it better to test the code you actually ship?
Setting this up with Jest is straightforward. First, you install the necessary packages.
npm install msw jest --save-dev
# or
yarn add msw jest --dev
Next, you define what your mock API looks like. You create “handlers” that specify which URLs to intercept and what to send back. You can organize these in a dedicated file.
// src/mocks/handlers.js
import { http, HttpResponse } from 'msw';
export const handlers = [
// Intercept GET requests to /api/user
http.get('/api/user', () => {
return HttpResponse.json({
id: 'user-123',
name: 'Test User',
email: '[email protected]'
});
}),
// Intercept POST requests to /api/login
http.post('/api/login', async ({ request }) => {
const { email } = await request.json();
// Simulate a successful login for a specific email
if (email === '[email protected]') {
return HttpResponse.json({ token: 'fake-jwt-token' });
}
// Simulate a failed login for others
return new HttpResponse(null, { status: 401 });
}),
];
Now, how do we tell Jest to use these handlers? You need to set up a server for your test environment (Node.js) and configure it in your Jest setup files.
// src/mocks/server.js
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
// This creates a request interception server for Node.js
export const server = setupServer(...handlers);
// jest.setup.js (configured in your jest.config.js)
import { server } from './src/mocks/server.js';
// Start the server before all tests
beforeAll(() => server.listen());
// Reset handlers after each test (important for test isolation)
afterEach(() => server.resetHandlers());
// Close the server after all tests are done
afterAll(() => server.close());
With this setup, every test file in your suite is now covered. Any request your code makes to /api/user will receive the mock JSON response. But what makes this truly powerful is the control you have within individual tests. You can override the default handlers for specific scenarios right where you need them.
Imagine you’re testing your user profile component. It fetches user data on mount. Your default handler returns a happy path response. But you also need to test the loading state and the error state. How would you do that without touching your component’s internal logic? With MSW and Jest, it’s simple.
// UserProfile.test.jsx
import { render, screen, waitFor } from '@testing-library/react';
import UserProfile from './UserProfile';
import { server } from '../mocks/server';
import { http, HttpResponse } from 'msw';
describe('UserProfile', () => {
test('displays user data on successful fetch', async () => {
render(<UserProfile />);
// The default handler provides the data
await waitFor(() => {
expect(screen.getByText('Test User')).toBeInTheDocument();
});
});
test('displays loading indicator', () => {
render(<UserProfile />);
// Immediately check for loading state before fetch resolves
expect(screen.getByRole('status')).toBeInTheDocument();
});
test('displays error message on network failure', async () => {
// Override the default handler JUST for this test
server.use(
http.get('/api/user', () => {
return new HttpResponse(null, { status: 500 });
})
);
render(<UserProfile />);
await waitFor(() => {
expect(screen.getByText(/failed to load/i)).toBeInTheDocument();
});
});
});
See how clean that is? The test for the error state doesn’t require you to mock a module or use a complex spy. You simply tell MSW, “for this test only, when you see this request, respond with a 500 error.” Your component’s error handling logic gets exercised with a real network response. This is the kind of test that gives you real confidence.
The benefits extend beyond frontend tests. If you’re writing a Node.js service that calls another API, you can use the same msw/node server to intercept those outgoing calls. Your service tests become fast, deterministic, and no longer depend on the health of a third-party service. You can simulate slow responses to test timeouts, or specific error payloads to ensure your service handles them gracefully.
This approach fundamentally changes your testing strategy. Your mocks are declarations of what your API contract is, not instructions on how to fake a function. When the backend API changes, you often only need to update your central handler files. Your tests become documentation for the API integration itself. They tell a clear story: “When my app asks for X, it expects Y back, and here’s how it behaves if something goes wrong.”
I’ve found that adopting Jest with MSW reduces the friction of writing integration tests. It turns a tedious task into a productive one. You spend less time wrestling with mock setups and more time thinking about edge cases and user flows. Your test suite runs faster because there’s no network latency, and it’s more reliable because the network conditions are controlled.
So, the next time you find yourself reaching for jest.spyOn to mock a fetch call, pause. Consider if you’re testing an implementation detail or the actual behavior. By letting your code run naturally and intercepting its requests, you create a stronger safety net. Your future self, and your teammates, will thank you when those tests catch a regression that simpler mocks would have missed.
What has your experience been with testing API-dependent code? Have you tried intercepting requests at the network level? I’d love to hear what works for your team. If you found this breakdown helpful, please share it with a colleague who might be battling flaky API tests. Let’s build more reliable software, one test at a time. Feel free to leave a comment below with your thoughts or questions.
As a best-selling author, I invite you to explore my books on Amazon. Don’t forget to follow me on Medium and show your support. Thank you! Your support means the world!
101 Books
101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as $4—making quality knowledge accessible to everyone.
Check out our book Golang Clean Code available on Amazon.
Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!
📘 Checkout my latest ebook for free on my channel!
Be sure to like, share, comment, and subscribe to the channel!
Our Creations
Be sure to check out our creations:
Investor Central | Investor Central Spanish | Investor Central German | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | JS Schools
We are on Medium
Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva