Тестирование данных
Как тестировать работу с API и состоянием.
Инструменты
- Jest — тест-раннер
- React Native Testing Library — тестирование компонентов
- MSW — моки API
- @testing-library/jest-native — матчеры
Установка
npm install --save-dev @testing-library/react-native jest @types/jest
npm install --save-dev msw
Тест компонента
import { render, screen, fireEvent, waitFor } from '@testing-library/react-native';
import { Posts } from './Posts';
test('отображает список постов', async () => {
render(<Posts />);
await waitFor(() => {
expect(screen.getByText('Первый пост')).toBeTruthy();
});
});
Мок API через MSW
// mocks/handlers.ts
import { http, HttpResponse } from 'msw';
export const handlers = [
http.get('https://api.example.com/posts', () => {
return HttpResponse.json([
{ id: 1, title: 'Первый пост' },
{ id: 2, title: 'Второй пост' },
]);
}),
];
// mocks/server.ts
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);
// setup.ts
import { server } from './mocks/server';
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
beforeAll(() => server.close());
Обёртка с провайдерами
function renderWithProviders(ui: React.ReactElement) {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
return render(
<QueryClientProvider client={queryClient}>
<NavigationContainer>{ui}</NavigationContainer>
</QueryClientProvider>
);
}
Тест формы
import { render, screen, fireEvent, waitFor } from '@testing-library/react-native';
test('отправляет форму', async () => {
const onSubmit = jest.fn();
render(<Form onSubmit={onSubmit} />);
fireEvent.changeText(screen.getByPlaceholderText('Email'), 'test@example.com');
fireEvent.press(screen.getByText('Отправить'));
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({ email: 'test@example.com' });
});
});
Тест валидации
test('показывает ошибку при пустом поле', async () => {
render(<Form onSubmit={jest.fn()} />);
fireEvent.press(screen.getByText('Отправить'));
expect(await screen.findByText('Email обязателен')).toBeTruthy();
});
Мок навигации
const mockNavigate = jest.fn();
jest.mock('@react-navigation/native', () => ({
...jest.requireActual('@react-navigation/native'),
useNavigation: () => ({ navigate: mockNavigate, goBack: jest.fn() }),
useRoute: () => ({ params: { id: 5 } }),
}));
Тест хука
import { renderHook, waitFor } from '@testing-library/react-native';
test('usePosts загружает посты', async () => {
const { result } = renderHook(() => usePosts(), {
wrapper: QueryWrapper,
});
await waitFor(() => expect(result.current.data).toBeDefined());
expect(result.current.data).toHaveLength(2);
});
Мок SecureStore
jest.mock('expo-secure-store', () => ({
getItemAsync: jest.fn((key) => Promise.resolve(key === 'token' ? 'test-token' : null)),
setItemAsync: jest.fn(),
deleteItemAsync: jest.fn(),
}));
Мок NetInfo
jest.mock('@react-native-community/netinfo', () => ({
addEventListener: jest.fn((cb) => {
cb({ isConnected: true });
return jest.fn();
}),
}));
Что тестировать
- Рендер компонентов
- Загрузка и ошибки
- Отправка формы
- Валидация
- Навигация
- Хуки
Что не тестировать
- Внутренние детали реализации
- Стили
- Сторонние библиотеки
Итоги
- React Native Testing Library
- MSW для моков API
- Обёртка с провайдерами
- Мок навигации и SecureStore
- Тестируй поведение, не реализацию