This guide covers comprehensive testing approaches for each state management solution, including:
- Unit Testing
- Integration Testing
- Component Testing
- Performance Testing
import '@testing-library/jest-native/extend-expect';
import { cleanup } from '@testing-library/react-native';
// Global setup
beforeEach(() => {
jest.useFakeTimers();
});
// Global teardown
afterEach(() => {
cleanup();
jest.clearAllMocks();
});
import { render } from '@testing-library/react-native';
import { Provider as ReduxProvider } from 'react-redux';
import { RecoilRoot } from 'recoil';
import { StoreProvider as MobXProvider } from '../store/mobx/StoreProvider';
import { Provider as JotaiProvider } from 'jotai';
// Redux wrapper
export const renderWithRedux = (ui: React.ReactElement, { store } = {}) => {
return render(<ReduxProvider store={store}>{ui}</ReduxProvider>);
};
// MobX wrapper
export const renderWithMobX = (ui: React.ReactElement, { store } = {}) => {
return render(<MobXProvider store={store}>{ui}</MobXProvider>);
};
// Recoil wrapper
export const renderWithRecoil = (ui: React.ReactElement) => {
return render(<RecoilRoot>{ui}</RecoilRoot>);
};
// Jotai wrapper
export const renderWithJotai = (ui: React.ReactElement) => {
return render(<JotaiProvider>{ui}</JotaiProvider>);
};
import { renderWithRedux, fireEvent } from '@testing-library/react-native';
import { configureStore } from '@reduxjs/toolkit';
import { ReduxScreen } from '../../../screens/ReduxScreen';
import { counterSlice } from '../slices/counterSlice';
describe('Redux Integration', () => {
const store = configureStore({
reducer: {
counter: counterSlice.reducer,
},
});
it('should handle counter interactions', () => {
const { getByText } = renderWithRedux(<ReduxScreen />, { store });
fireEvent.press(getByText('Increment'));
expect(getByText('Counter: 1')).toBeTruthy();
fireEvent.press(getByText('Decrement'));
expect(getByText('Counter: 0')).toBeTruthy();
});
// Async action testing
it('should handle async actions', async () => {
const { result } = renderHook(() => useAppDispatch());
await act(async () => {
await result.current(fetchTodos());
});
expect(store.getState().todos.items).toHaveLength(3);
});
});
import { TodoStore } from '../store';
import { reaction } from 'mobx';
describe('MobX Store', () => {
let store: TodoStore;
let reactionSpy: jest.Mock;
beforeEach(() => {
store = new TodoStore();
reactionSpy = jest.fn();
});
it('should track reactions', () => {
reaction(
() => store.todos.length,
(length) => reactionSpy(length)
);
store.addTodo('Test');
expect(reactionSpy).toHaveBeenCalledWith(1);
});
// Component testing
it('should render and update correctly', () => {
const { getByText } = renderWithMobX(<MobXScreen />, { store });
fireEvent.press(getByText('Add Todo'));
expect(getByText('Total: 1')).toBeTruthy();
});
});
import { renderHook, act } from '@testing-library/react-hooks';
import { createStore } from '../store';
describe('Zustand Store', () => {
it('should handle state updates', () => {
const { result } = renderHook(() => createStore());
act(() => {
result.current.getState().increment();
});
expect(result.current.getState().count).toBe(1);
});
// Middleware testing
it('should handle middleware', () => {
const logSpy = jest.fn();
const store = createStore(logSpy);
act(() => {
store.getState().increment();
});
expect(logSpy).toHaveBeenCalled();
});
});
import { renderHook, act } from '@testing-library/react-hooks';
import { RecoilRoot, useRecoilState } from 'recoil';
import { counterState } from '../atoms';
describe('Recoil Atoms', () => {
const wrapper = ({ children }) => <RecoilRoot>{children}</RecoilRoot>;
it('should handle atom updates', () => {
const { result } = renderHook(() => useRecoilState(counterState), {
wrapper,
});
act(() => {
result.current[1](1);
});
expect(result.current[0]).toBe(1);
});
// Selector testing
it('should compute derived state', () => {
const { result } = renderHook(() => useRecoilValue(todoStatsState), {
wrapper,
});
expect(result.current.total).toBe(0);
});
});
import { renderHook, act } from '@testing-library/react-hooks';
import { useStore, StoreProvider } from '../context';
describe('Context Store', () => {
const wrapper = ({ children }) => (
<StoreProvider>{children}</StoreProvider>
);
it('should handle context updates', () => {
const { result } = renderHook(() => useStore(), { wrapper });
act(() => {
result.current.dispatch({ type: 'INCREMENT' });
});
expect(result.current.state.count).toBe(1);
});
});
import { renderHook, act } from '@testing-library/react-hooks';
import { useAtom } from 'jotai';
import { counterAtom } from '../atoms';
describe('Jotai Atoms', () => {
it('should handle atom updates', () => {
const { result } = renderHook(() => useAtom(counterAtom));
act(() => {
result.current[1](1);
});
expect(result.current[0]).toBe(1);
});
});
import { performance } from 'perf_hooks';
describe('Performance Tests', () => {
it('should measure state update performance', async () => {
const start = performance.now();
// Perform 1000 state updates
for (let i = 0; i < 1000; i++) {
await store.dispatch(increment());
}
const duration = performance.now() - start;
expect(duration).toBeLessThan(1000); // Should complete within 1 second
});
});
describe('State Management E2E', () => {
beforeAll(async () => {
await device.launchApp();
});
it('should handle counter interactions', async () => {
await element(by.text('Increment')).tap();
await expect(element(by.text('Counter: 1'))).toBeVisible();
});
});
-
Test Organization
- Group tests by feature
- Use descriptive test names
- Follow AAA pattern (Arrange, Act, Assert)
- Keep tests focused and simple
-
Test Coverage
- Aim for high coverage of business logic
- Test edge cases
- Include error scenarios
- Test async operations
-
Performance Testing
- Measure render times
- Test state update performance
- Monitor memory usage
- Test with large datasets
-
Integration Testing
- Test component interactions
- Verify state updates
- Test side effects
- Validate UI updates