Mocking APIs
Mocking APIs
MSW (Recommended)
// src/mocks/handlers.js
import { http, HttpResponse } from 'msw';
export const handlers = [
http.get('/api/users', () => {
return HttpResponse.json([
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' },
]);
}),
http.post('/api/users', async ({ request }) => {
const body = await request.json();
return HttpResponse.json({ id: 3, ...body }, { status: 201 });
}),
http.get('/api/users/:id', ({ params }) => {
return HttpResponse.json({ id: params.id, name: 'John' });
}),
];
Mock Service Worker Setup
// src/mocks/server.js
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);
// src/test/setup.js
import { server } from '../mocks/server';
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
Custom Handlers in Tests
describe('UserList', () => {
it('should handle empty list', async () => {
server.use(
http.get('/api/users', () => {
return HttpResponse.json([]);
})
);
render(<UserList />);
await waitFor(() => {
expect(screen.getByText('No users found')).toBeInTheDocument();
});
});
it('should handle error', async () => {
server.use(
http.get('/api/users', () => {
return HttpResponse.json(null, { status: 500 });
})
);
render(<UserList />);
await waitFor(() => {
expect(screen.getByText('Error loading users')).toBeInTheDocument();
});
});
});
Fetch Mock
// Simple fetch mock
beforeEach(() => {
global.fetch = jest.fn();
});
afterEach(() => {
jest.restoreAllMocks();
});
it('should fetch data', async () => {
global.fetch.mockResolvedValueOnce({
ok: true,
json: async () => ({ data: 'test' }),
});
const result = await fetchData();
expect(result).toEqual({ data: 'test' });
expect(global.fetch).toHaveBeenCalledWith('/api/data');
});
Mocking Modules
Mocking Modules
jest.mock
// Mock entire module
jest.mock('./api');
import { fetchUsers } from './api';
beforeEach(() => {
fetchUsers.mockClear();
});
it('should call fetchUsers', async () => {
fetchUsers.mockResolvedValue([{ id: 1, name: 'John' }]);
render(<UserList />);
await waitFor(() => {
expect(fetchUsers).toHaveBeenCalled();
});
});
Partial Mock
// Mock specific methods
jest.mock('./utils', () => ({
...jest.requireActual('./utils'),
formatDate: jest.fn(() => '2024-01-01'),
}));
import { formatDate, otherFunction } from './utils';
it('should use mocked formatDate', () => {
expect(formatDate()).toBe('2024-01-01');
// otherFunction still uses real implementation
});
Mock Component
// Mock child component
jest.mock('./ChildComponent', () => {
return function MockChild({ name }) {
return <div data-testid="mock-child">Mock: {name}</div>;
};
});
// Mock with factory
jest.mock('./HeavyComponent', () => {
return {
__esModule: true,
default: jest.fn(() => <div>Mocked</div>),
helperFunction: jest.fn(() => 'mocked'),
};
});
Mock Router
// Mock react-router-dom
const mockNavigate = jest.fn();
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useNavigate: () => mockNavigate,
useParams: () => ({ id: '1' }),
}));
it('should navigate on click', () => {
render(<MyComponent />);
fireEvent.click(screen.getByText('Go to page'));
expect(mockNavigate).toHaveBeenCalledWith('/page/1');
});
Mocking Browser APIs
Mocking Browser APIs
window.matchMedia
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: query === '(prefers-color-scheme: dark)',
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
IntersectionObserver
const mockIntersectionObserver = jest.fn();
mockIntersectionObserver.mockReturnValue({
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn(),
});
window.IntersectionObserver = mockIntersectionObserver;
ResizeObserver
window.ResizeObserver = class ResizeObserver {
constructor(callback) {
this.callback = callback;
}
observe() {}
unobserve() {}
disconnect() {}
};
localStorage
const localStorageMock = {
getItem: jest.fn(),
setItem: jest.fn(),
removeItem: jest.fn(),
clear: jest.fn(),
};
Object.defineProperty(window, 'localStorage', {
value: localStorageMock,
});
window.location
delete window.location;
window.location = {
href: 'http://localhost:3000',
pathname: '/',
search: '',
hash: '',
assign: jest.fn(),
reload: jest.fn(),
replace: jest.fn(),
};
Navigator.geolocation
Object.defineProperty(navigator, 'geolocation', {
value: {
getCurrentPosition: jest.fn((success) =>
success({ coords: { latitude: 0, longitude: 0 } })
),
},
});
Best Practices
- Mock at the boundary (API, not internal)
- Use MSW for API mocking
- Mock browser APIs in setup file
- Clear mocks between tests
- Don't mock what you don't own
Quiz
1. What is MSW (specific to mocking)?
2. When should you mock?
3. What is a common mistake when implementing Mocking in frontend applications?
Flashcards
Question
What is mocking?
Click to reveal answer
Answer
Creating fake implementations of functions or modules to isolate tests.
Question
When should you mock APIs?
Click to reveal answer
Answer
When testing components that depend on external services, to isolate and control behavior.
Question
What is MSW?
Click to reveal answer
Answer
Mock Service Worker - intercepts API requests for realistic testing without mocking fetch.
Question
What should you NOT mock?
Click to reveal answer
Answer
Internal functions and modules you own - only mock external dependencies.
Revision Notes
Key Takeaways
- 1. MSW is the recommended way to mock APIs
- 2. Mock external dependencies, not internal code
- 3. Mock browser APIs in setup files
- 4. Clear mocks between tests
- 5. Use partial mocks when needed
Interview Tips
- • Explain when and why to mock
- • Discuss MSW vs jest.mock for API mocking
- • Know how to mock browser APIs
Cheat Sheet
Mocking Cheat Sheet
MSW
http.get('/api/users', () => {
return HttpResponse.json([{ id: 1 }]);
});
jest.mock
jest.mock('./api');
api.fetchUsers.mockResolvedValue(data);
Browser APIs
- matchMedia
- IntersectionObserver
- localStorage
- navigator.geolocation
Best Practices
- Mock at boundaries
- Clear mocks between tests
- Don't mock what you don't own