Unit Test Development for React Native Application (Jest)
React Native projects often come with Jest config in package.json, one smoke test renders correctly and zero business logic coverage. Jest + React Native Testing Library provide everything needed — question is what and how to test.
Setup and Stack
{
"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 — most common reason for broken tests. Native modules in node_modules written in ES modules without transpile. Pattern must include all react-native-* packages used in project.
Stack:
- Jest — runner
- @testing-library/react-native — component render, fireEvent
- @testing-library/user-event — user simulation
- msw (Mock Service Worker) — mock API without fetch/axios substitution
Hook Testing
Custom hooks — first thing to cover. renderHook from @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() mandatory for any state update inside hook — without Jest emits warning and test may pass incorrectly.
Zustand and Redux Toolkit
Zustand tests directly:
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 — via configureStore with real reducer and mock middleware:
const store = configureStore({ reducer: { user: userReducer } });
store.dispatch(setUser({ id: '1', name: 'Test' }));
expect(store.getState().user.current?.name).toBe('Test');
Don't mock entire store — makes test meaningless.
API Mocking via MSW
MSW intercepts fetch/axios at network level — mock realistic without module substitution:
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());
For error testing: server.use(rest.get(..., (_, res, ctx) => res(ctx.status(500)))).
Mocking Native Modules
react-native-async-storage, @react-native-firebase/app, react-native-permissions — all native code that crashes Jest (no JS implementation). Each such module needs mocking:
// __mocks__/@react-native-async-storage/async-storage.js
// Use official mock from package:
jest.mock('@react-native-async-storage/async-storage',
() => require('@react-native-async-storage/async-storage/jest/async-storage-mock')
);
Firebase mock via @firebase/rules-unit-testing or completely via jest.mock('@react-native-firebase/auth', () => ({...})).
What's Worth Testing
- Business logic in hooks and stores — mandatory
- Utilities and data transformations — mandatory
- Navigation (parameters, conditional transitions) — desirable via
@react-navigation/testing-library - UI component render with snapshot — only for design system, not business screens
Timeframe: 3–5 days depending on project size and current Jest config state.







