Розробка Unit-тестів для React Native-додатку (Jest)
React Native-проекти часто приходять з Jest-конфігом у package.json, одним smoke-тестом renders correctly та нульовим покриттям бізнес-логіки. Jest + React Native Testing Library дають все необхідне — питання в тому, що та як тестувати.
Конфігурація та стек
{
"jest": {
"preset": "react-native",
"setupFilesAfterFramework": ["@testing-library/react-native/extend-expect"],
"moduleNameMapper": {
"^@/(.*)$": "<rootDir>/src/$1"
},
"transformIgnorePatterns": [
"node_modules/(?!(react-native|@react-native|react-native-.*)/)"
]
}
}
transformIgnorePatterns — найчастіша причина сломаних тестів. Нативні модулі у node_modules написані на ES modules без transpile. Паттерн повинен включати всі react-native-* пакети, які використовуються в проекті.
Стек:
- Jest — runner
- @testing-library/react-native — рендер компонентів, fireEvent
- @testing-library/user-event — симуляція користувача
-
msw (Mock Service Worker) — мок API без підмени
fetch/axios
Тестування хуків
Кастомні хуки — перше, що потрібно покрити. 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() обов'язковий для будь-якого оновлення state всередину хука — без нього Jest видає попередження та тест може пройти некоректно.
Zustand та Redux Toolkit
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 та мок-middleware:
const store = configureStore({ reducer: { user: userReducer } });
store.dispatch(setUser({ id: '1', name: 'Test' }));
expect(store.getState().user.current?.name).toBe('Test');
Не мокуйте весь store — робить тест бесмисленним.
Мокування API через MSW
MSW перехоплює fetch/axios на рівні мережі — мок реалістичен без підмени модулів:
import { setupServer } from 'msw/node';
import { rest } from 'msw';
const server = setupServer(
rest.get('https://api.example.com/user/:id', (req, res, ctx) => {
return res(ctx.json({ id: req.params.id, name: 'Test User' }));
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
Для тестування помилок: server.use(rest.get(..., (_, res, ctx) => res(ctx.status(500)))).
Мокування нативних модулів
react-native-async-storage, @react-native-firebase/app, react-native-permissions — все це нативний код, який падає в Jest (немає JS-реалізації). Кожен такий модуль потрібно мокувати:
// __mocks__/@react-native-async-storage/async-storage.js
// Використовуємо офіційний mock з пакету:
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', () => ({...})).
Що реально стоїть тестувати
- Бізнес-логіка в хуках та сторах — обов'язково
- Утиліти та трансформації даних — обов'язково
- Навігація (параметри, умовні переходи) — бажано через
@react-navigation/testing-library - Рендер UI-компонентів зі snapshot — лише для дизайн-системи, не для бізнес-екранів
Срок: 3–5 днів залежно від розміру проекту та поточного стану конфігурації Jest.







