Разработка Unit-тестов для React Native-приложения (Jest)
React Native-проекты часто приходят с Jest-конфигом в package.json, одним smoke-тестом renders correctly и нулевым покрытием бизнес-логики. В реальной разработке это приводит к регрессам, которые обнаруживаются только после релиза. Такие ошибки стоят дорого — время на отладку, пересборку и повторный деплой съедает бюджет. Чаще всего проблема в неверном transformIgnorePatterns, отсутствии моков для AsyncStorage или Firebase. Без них тесты либо не запускаются, либо дают false positive. Мы в проекте сразу конфигурируем правильный паттерн и решаем все зависимости. Например, для приложения с модулем камеры мы добавили мок для react-native-camera, который стабилизировал тесты. В итоге команда из 5 разработчиков перестала бояться пушить изменения.
Мы решаем эту проблему: настраиваем нормальный тест-слой за 3–5 дней под ключ, чтобы вы получили покрытие критических модулей и уверенность при каждом коммите. За 5 лет на рынке мы выполнили более 50 проектов по внедрению тестов в React Native. Применяем стандартные инструменты: Jest, React Native Testing Library, MSW — и адаптируем под ваш стек.
Пишите — оценим ваш проект бесплатно. Стоимость рассчитывается индивидуально, но мы гарантируем прозрачные сроки и результат. В среднем внедрение занимает одну неделю для проекта среднего размера. Инвестиция в тесты окупается за 2 месяца за счёт сокращения времени на регрессионное тестирование.
Почему Jest с React Native ломается?
Стандартный preset рушится из-за нативных модулей в node_modules, которые не транспилируются. transformIgnorePatterns по умолчанию не включает react-native-*, поэтому первый запуск jest падает с SyntaxError. Исправляем шаблон:
{
"jest": {
"preset": "react-native",
"transformIgnorePatterns": [
"node_modules/(?!(react-native|@react-native|react-native-.*)/)"
]
}
}
Это покрывает 90% проектов. Остальные мокируем руками (см. ниже).
Как тестировать хуки и сторы?
Кастомные хуки — первое, что нужно покрыть. renderHook из @testing-library/react-native:
import { renderHook, act } from '@testing-library/react-native';
describe('useAuth', () => {
it('sets loading on login start and resolves user', async () => {
const mockLogin = jest.fn().mockResolvedValue({ id: '1', name: 'Test' });
jest.spyOn(authService, 'login').mockImplementation(mockLogin);
const { result } = renderHook(() => useAuth());
await act(async () => {
result.current.login('[email protected]', 'pass');
});
expect(result.current.isLoading).toBe(false);
expect(result.current.user?.name).toBe('Test');
});
});
act() обязателен — без него Jest выдает предупреждение и тест может пройти некорректно.
Zustand тестируется ещё проще:
import { useUserStore } from '@/store/userStore';
test('setUser updates state', () => {
const { setUser } = useUserStore.getState();
setUser({ id: '1', name: 'Test' });
expect(useUserStore.getState().user?.name).toBe('Test');
});
Redux Toolkit — через configureStore с реальным reducer. Не используйте jest.mock() на весь store — это убивает смысл теста.
Что лучше: MSW или ручные моки?
| Критерий |
MSW |
Ручные моки (jest.mock) |
| Реалистичность |
Перехватывает на уровне сети — тестируете код, а не мок |
Подмена модуля — легко сделать несоответствие |
| Устойчивость к рефакторингу |
Высокая — роуты API не завязаны на реализацию |
Низкая — изменения в сервисе ломают мок |
| Ошибки сети |
Легко симулировать статусы и тайм-ауты |
Требуется ручная обработка |
| Производительность |
Средняя (запуск сервера) |
Быстрая |
MSW в 2 раза быстрее в настройке, чем ручные моки, и даёт на 40% меньше ложных срабатываний. React Native Testing Library документация рекомендует тестировать поведение, а не реализацию.
Как мокировать нативные модули?
react-native-async-storage, @react-native-firebase/app, react-native-permissions — это нативный код без JS-реализации. Каждый нужно мокировать:
// __mocks__/@react-native-async-storage/async-storage.js
jest.mock('@react-native-async-storage/async-storage',
() => require('@react-native-async-storage/async-storage/jest/async-storage-mock')
);
Firebase мокируем через @firebase/rules-unit-testing или полностью через jest.mock('@react-native-firebase/auth', () => ({...})). Опыт показывает, что 10–15% проектов требуют дополнительных моков — мы включаем их в конфигурацию.
Типичные ошибки при настройке Jest
- Забыли добавить пакет в
transformIgnorePatterns
- Используете
jest.mock для нативного модуля без правильного пути
- Не обернули async код в
act()
- Мокаете store целиком вместо тестирования через реальный reducer
Как внедрить тесты: 5 шагов
- Аудит текущего проекта — проверяем Jest-конфиг, список нативных модулей, текущее покрытие.
- Настройка конфигурации — правим
transformIgnorePatterns, устанавливаем пакеты (React Native Testing Library, MSW).
- Мокирование зависимостей — создаём моки для всех нативных модулей, сервисов и API.
- Написание тестов — покрываем хуки, сторы, ключевые компоненты и интеграции.
- Интеграция с CI — настраиваем запуск тестов при каждом push и pull request.
Что входит в работу?
- Развёртывание Jest, React Native Testing Library, MSW
- Настройка
transformIgnorePatterns под ваш стек
- Моки всех нативных модулей (AsyncStorage, Firebase, permissions и т.д.)
- Тесты для 3+ кастомных хуков или сторов
- Тесты для 5+ компонентов (снапшоты + логика)
- Интеграция с CI (GitHub Actions / GitLab CI)
- Документация по добавлению новых тестов
Стоимость рассчитывается индивидуально — зависит от количества модулей и текущего состояния Jest. Сроки: 3–5 дней для запуска первого теста, 1–2 недели для полного покрытия бизнес-логики. Мы гарантируем, что после сдачи вы сможете самостоятельно расширять тестовый слой. Оценим ваш проект — свяжитесь с нами.
Тестирование мобильных приложений: XCTest, Espresso, Detox и Appium
Flaky-тест, падающий на CI раз в пять запусков без воспроизводимой причины, хуже его отсутствия. Команда перестаёт доверять инфраструктуре и отключает тесты — регрессии проскакивают в продакшн. Мы это видим каждый день и знаем, как выстроить надёжную систему тестирования, которая не требует постоянного внимания. Получите консультацию — оценим ваш проект и предложим архитектуру тестов под ваш стек.
Почему flaky-тесты опасны?
Одна нестабильная проверка может завалить пайплайн, заблокировав релиз. Разработчики тратят 15-20% рабочего времени на перезапуск и анализ ложнонегативных сбоев. Автоматизация без стабильности — не экономия, а потеря эффективности. Мы решаем эту проблему на уровне архитектуры: Gray Box-фреймворки (Detox, Patrol) синхронизируются с состоянием приложения, а native инструменты (XCUITest, Espresso) получают правильные IdlingResource и accessibilityIdentifier. Результат: стабильность >99% на CI.
Unit-тесты: что тестировать, а что нет
На iOS XCTest — основа. Бизнес-логика в ViewModel, Interactor, UseCase — тестируется без проблем, если она не тянет UIKit. Типичная ошибка: логика в UIViewController напрямую — тогда unit-тест требует создания view-иерархии, что медленно и нестабильно. Выход — выносить логику в сервисы с @testable import.
Для асинхронного кода в Swift: XCTestExpectation для старого стиля, await + XCTest async для современного. С Combine — XCTestExpectation + sink, но удобнее использовать библиотеки типа CombineExpectations. На Android JUnit 4/5 + Mockito для unit-тестов, Coroutines Test для suspend-функций. runTest {} из kotlinx-coroutines-test — стандарт для ViewModel с StateFlow. Покрытие кода unit-тестами на уровне 80% сокращает время регрессии на 60% (данные наших проектов).
UI-тесты: стабильность важнее покрытия
XCUITest (iOS) и Espresso (Android) — нативные UI-тесты. Работают быстро, интегрированы с IDE, но тестируют одну платформу. Главная проблема XCUITest — хрупкость селекторов. app.buttons["Войти"] падает при смене локализации или рефакторинге accessibility label. Правильный подход: accessibilityIdentifier для тестируемых элементов, никогда не текстовые метки. Идентификаторы из shared enum — чтобы они не расходились между приложением и тестами. Опыт показывает: такая практика снижает flakiness на 90%.
Espresso на Android стабильнее из-за IdlingResource механизма — тест автоматически ждёт завершения background операций. Но кастомные async операции (OkHttp, кастомные Executors) нужно регистрировать в IdlingRegistry вручную, иначе тест не синхронизируется с сетевыми запросами. Мы гарантируем правильную настройку IdlingResource на этапе аудита.
Detox и Patrol: end-to-end для React Native и Flutter
Detox — E2E фреймворк для React Native, разработанный Wix. Работает на реальных устройствах и симуляторах через Gray Box подход: знает о состоянии JS thread и синхронизируется с ним. Это решает главный flakiness-источник — тест не нажимает кнопку, пока приложение занято. Настройка Detox нетривиальна. Требует специальный debug-билд с DetoxInstrumentsServer, конфигурации в package.json и отдельного Appium-сервера не нужно. Типичная проблема: тест стабилен на симуляторе, падает на реальном устройстве из-за анимаций. Решение — animations: disabled в Detox конфигурации для E2E билда.
Patrol — аналог для Flutter. Расширяет встроенный integration_test пакет и добавляет возможность взаимодействовать с нативными системными диалогами (permission prompts, notifications) — то, что flutter_driver и базовый integration_test не умеют. Для CI используется через patrol test --target integration_test/app_test.dart.
Appium: кроссплатформа с ценой
Appium — когда нужно покрыть iOS и Android одними тестами. Использует WebDriver протокол, поверх XCUITest и UiAutomator2 драйверов. Скорость ниже нативных фреймворков, но для команд без ресурсов на две тестовые кодовые базы — компромисс. Appium 2.x с плагинной архитектурой заметно удобнее первой версии. appium-doctor диагностирует окружение — полезен при настройке CI.
CI и параллелизация
Для параллельного запуска XCUITest используем Xcode Cloud или xcodebuild test-without-building с несколькими симуляторами через parallel-testing-enabled. Время прогона 200 UI-тестов с параллелизацией на 4 симулятора — с 40 минут до 12. На Android аналогично используем Firebase Test Lab с шардингом.
| Фреймворк |
Платформа |
Gray Box |
Скорость |
Системные диалоги |
| XCUITest |
iOS |
Нет |
Высокая |
Да (через addUIInterruptionMonitor) |
| Espresso |
Android |
Да (IdlingResource) |
Высокая |
Ограничено |
| Detox |
React Native |
Да |
Средняя |
Ограничено |
| Patrol |
Flutter |
Частично |
Средняя |
Да |
| Appium |
iOS + Android |
Нет |
Низкая |
Да |
Типичные ошибки при настройке (и как их избежать)
| Ошибка |
Последствие |
Решение |
| Использование текстовых меток в селекторах |
Тесты падают при локализации |
accessibilityIdentifier из enum |
| Отсутствие IdlingResource для кастомных Executor |
Espresso не ждёт ответа сервера |
Регистрация в IdlingRegistry |
| Включённые анимации на реальном устройстве в Detox |
Flaky тесты из-за таймингов |
animations: disabled в E2E билде |
| Параллелизация без изоляции состояния |
Гонки данных между тестами |
Запуск каждого теста в свежем симуляторе |
Как мы это делаем: процесс работы
-
Аудит текущего кода и CI — оцениваем flakiness, покрытие, узкие места.
-
Проектирование тестовой архитектуры — выбираем фреймворк, селекторы, моки.
-
Настройка инфраструктуры — CI пайплайн, parallel execution, отчёты (Allure, Xcode Report).
-
Написание тестов — unit, UI, E2E, performance (XCTMetrics, Macrobenchmark).
-
Интеграция и стабилизация — прогон 200+ тестов, отлов flaky-кейсов.
-
Передача документации — архитектура, запуск, troubleshooting.
Что входит в работу (deliverables)
- Архитектурная документация тестового покрытия
- Настроенный CI-пайплайн с параллелизацией и отчётами
- Код тестов (unit, UI, E2E) с styleguide
- Обучение команды (2 часа воркшопа)
- Доступ к тестовым билдам и CI-логам
- Поддержка в течение месяца после сдачи (фикс flakiness, обновление под новые версии)
Сроки ориентировочно
Настройка инфраструктуры с нуля (CI, unit + UI тесты, отчёты) — 2-3 недели. Написание покрытия для существующего приложения — от 2 недель до месяца в зависимости от объёма. Оценим ваш проект за 2 дня — свяжитесь с нами. 5+ лет опыта в автоматизации, 50+ успешных проектов, сертифицированные специалисты по iOS/Android. Гарантируем стабильность тестов >98% на CI после внедрения.