Перейти к содержимому

17. Моки в RTL

RTL Mocking

// Мок fetch глобально
global.fetch = jest.fn();
beforeEach(() => {
(global.fetch as jest.Mock).mockClear();
});
// Конфигурировать в каждом тесте
test('loads products', async () => {
(global.fetch as jest.Mock).mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve([
{ id: 1, name: 'Apple', price: 1.5 },
]),
});
render(<ProductList />);
expect(await screen.findByText('Apple')).toBeInTheDocument();
expect(screen.getByText('$1.50')).toBeInTheDocument();
});
// Замокировать axios
jest.mock('axios');
import axios from 'axios';
test('fetches user data', async () => {
(axios.get as jest.Mock).mockResolvedValue({
data: { id: 1, name: 'Alice' },
});
render(<UserProfile userId={1} />);
expect(await screen.findByText('Alice')).toBeInTheDocument();
expect(axios.get).toHaveBeenCalledWith('/api/users/1');
});
// Мок кастомного хука
jest.mock('../hooks/useAuth', () => ({
useAuth: () => ({
user: { name: 'Alice', role: 'admin' },
isAuthenticated: true,
logout: jest.fn(),
}),
}));
test('shows admin menu for admin', () => {
render(<Navbar />);
expect(screen.getByRole('link', { name: /admin panel/i })).toBeInTheDocument();
});
const mockNavigate = jest.fn();
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useNavigate: () => mockNavigate,
useParams: () => ({ id: '42' }),
}));
test('redirects after login', async () => {
const user = userEvent.setup();
render(
<MemoryRouter>
<LoginForm />
</MemoryRouter>
);
await user.type(screen.getByLabelText(/email/i), '[email protected]');
await user.type(screen.getByLabelText(/password/i), 'password123');
await user.click(screen.getByRole('button', { name: /login/i }));
await waitFor(() => {
expect(mockNavigate).toHaveBeenCalledWith('/dashboard');
});
});
// Мок localStorage
const localStorageMock = {
getItem: jest.fn(),
setItem: jest.fn(),
removeItem: jest.fn(),
clear: jest.fn(),
};
Object.defineProperty(window, 'localStorage', { value: localStorageMock });
test('saves theme to localStorage', async () => {
const user = userEvent.setup();
render(<ThemeSwitcher />);
await user.click(screen.getByRole('button', { name: /dark/i }));
expect(localStorageMock.setItem).toHaveBeenCalledWith('theme', 'dark');
});
test('loads saved theme', () => {
localStorageMock.getItem.mockReturnValue('dark');
render(<ThemeSwitcher />);
expect(screen.getByText(/current theme: dark/i)).toBeInTheDocument();
});
// Мок window.location
delete window.location;
window.location = { href: '', assign: jest.fn() };
test('redirects to external URL', async () => {
const user = userEvent.setup();
render(<ExternalLink url="https://example.com" />);
await user.click(screen.getByRole('link'));
expect(window.location.assign).toHaveBeenCalledWith('https://example.com');
});
// Мок IntersectionObserver (для lazy loading)
const mockIntersectionObserver = jest.fn().mockImplementation(() => ({
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn(),
}));
window.IntersectionObserver = mockIntersectionObserver;
// Мок ResizeObserver
window.ResizeObserver = jest.fn().mockImplementation(() => ({
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn(),
}));
Окно терминала
npm install --save-dev msw
handlers.js
import { http, HttpResponse } from 'msw';
export const handlers = [
http.get('/api/users', () => {
return HttpResponse.json([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
]);
}),
http.post('/api/users', async ({ request }) => {
const body = await request.json();
return HttpResponse.json({ id: 3, ...body }, { status: 201 });
}),
];
// setupServer.js (для Node.js тестов)
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);
// jest.setup.js
import { server } from './src/mocks/setupServer';
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
// В тестах можно переопределять:
test('handles server error', async () => {
server.use(
http.get('/api/users', () => {
return HttpResponse.json({ error: 'Server Error' }, { status: 500 });
})
);
render(<UserList />);
expect(await screen.findByRole('alert')).toHaveTextContent('Server Error');
});
  1. Настрой MSW для мокирования API в тестах компонентов
  2. Замокируй useAuth хук и протестируй защищённые маршруты
  3. Протестируй компонент, читающий из localStorage
  • Мокируй внешние зависимости (fetch, axios, Router)
  • MSW — лучший способ мокировать API (перехватывает на уровне сети)
  • jest.mock() для модулей, jest.spyOn() для методов объектов
  • localStorage, IntersectionObserver — мокируй в setupTests