From react-native-web
Provides Jest and React Native Testing Library patterns for testing React Native Web apps, including component testing, utilities, configuration, and best practices.
How this skill is triggered — by the user, by Claude, or both
Slash command
/react-native-web:react-native-web-testingThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Comprehensive testing patterns for React Native Web applications using Jest and React Native Testing Library.
Comprehensive testing patterns for React Native Web applications using Jest and React Native Testing Library.
The standard testing library for React Native components:
import { render, screen, fireEvent } from '@testing-library/react-native';
import { Button } from './Button';
describe('Button', () => {
it('calls onPress when pressed', () => {
const onPress = jest.fn();
render(<Button title="Click me" onPress={onPress} />);
const button = screen.getByText('Click me');
fireEvent.press(button);
expect(onPress).toHaveBeenCalledTimes(1);
});
});
Configure Jest for React Native Web:
// jest.config.js
module.exports = {
preset: 'react-native',
moduleNameMapper: {
'^react-native$': 'react-native-web',
},
transformIgnorePatterns: [
'node_modules/(?!(react-native|@react-native|react-native-web)/)',
],
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
};
Common testing utilities and helpers:
import { render, RenderOptions } from '@testing-library/react-native';
import { ReactElement } from 'react';
import { ThemeProvider } from './theme';
interface CustomRenderOptions extends Omit<RenderOptions, 'wrapper'> {
theme?: Theme;
}
export function renderWithProviders(
ui: ReactElement,
{ theme = defaultTheme, ...options }: CustomRenderOptions = {}
) {
return render(
<ThemeProvider value={theme}>
{ui}
</ThemeProvider>,
options
);
}
✅ Test user interactions and behavior:
import { render, screen, fireEvent, waitFor } from '@testing-library/react-native';
import { LoginForm } from './LoginForm';
describe('LoginForm', () => {
it('submits form with valid credentials', async () => {
const onSubmit = jest.fn();
render(<LoginForm onSubmit={onSubmit} />);
const emailInput = screen.getByPlaceholderText('Email');
const passwordInput = screen.getByPlaceholderText('Password');
const submitButton = screen.getByText('Login');
fireEvent.changeText(emailInput, '[email protected]');
fireEvent.changeText(passwordInput, 'password123');
fireEvent.press(submitButton);
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({
email: '[email protected]',
password: 'password123',
});
});
});
it('shows error for invalid email', async () => {
render(<LoginForm onSubmit={jest.fn()} />);
const emailInput = screen.getByPlaceholderText('Email');
const submitButton = screen.getByText('Login');
fireEvent.changeText(emailInput, 'invalid-email');
fireEvent.press(submitButton);
await waitFor(() => {
expect(screen.getByText('Invalid email address')).toBeTruthy();
});
});
});
✅ Use waitFor for async operations:
import { render, screen, waitFor } from '@testing-library/react-native';
import { UserProfile } from './UserProfile';
describe('UserProfile', () => {
it('loads and displays user data', async () => {
const mockUser = { id: '1', name: 'John Doe', email: '[email protected]' };
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve(mockUser),
})
) as jest.Mock;
render(<UserProfile userId="1" />);
// Check loading state
expect(screen.getByTestId('loading-indicator')).toBeTruthy();
// Wait for data to load
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeTruthy();
expect(screen.getByText('[email protected]')).toBeTruthy();
});
expect(screen.queryByTestId('loading-indicator')).toBeNull();
});
});
✅ Mock navigation and other dependencies:
import { render, screen, fireEvent } from '@testing-library/react-native';
// Mock navigation
const mockNavigate = jest.fn();
jest.mock('@react-navigation/native', () => ({
useNavigation: () => ({
navigate: mockNavigate,
}),
}));
describe('HomeScreen', () => {
it('navigates to details on item press', () => {
render(<HomeScreen />);
const item = screen.getByText('Item 1');
fireEvent.press(item);
expect(mockNavigate).toHaveBeenCalledWith('Details', { id: '1' });
});
});
import { renderHook, act } from '@testing-library/react-hooks';
import { useCounter } from './useCounter';
describe('useCounter', () => {
it('increments counter', () => {
const { result } = renderHook(() => useCounter());
expect(result.current.count).toBe(0);
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
it('decrements counter', () => {
const { result } = renderHook(() => useCounter(5));
act(() => {
result.current.decrement();
});
expect(result.current.count).toBe(4);
});
});
import { render, screen } from '@testing-library/react-native';
import { AuthProvider } from './auth-context';
import { ProtectedScreen } from './ProtectedScreen';
describe('ProtectedScreen', () => {
it('shows content when authenticated', () => {
const mockUser = { id: '1', name: 'John' };
render(
<AuthProvider initialUser={mockUser}>
<ProtectedScreen />
</AuthProvider>
);
expect(screen.getByText('Welcome, John')).toBeTruthy();
});
it('shows login prompt when not authenticated', () => {
render(
<AuthProvider initialUser={null}>
<ProtectedScreen />
</AuthProvider>
);
expect(screen.getByText('Please log in')).toBeTruthy();
});
});
import { render } from '@testing-library/react-native';
import { Card } from './Card';
describe('Card', () => {
it('matches snapshot', () => {
const { toJSON } = render(
<Card title="Test Card" description="Test description" />
);
expect(toJSON()).toMatchSnapshot();
});
});
import { render, screen, fireEvent, waitFor } from '@testing-library/react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { HomeScreen } from './HomeScreen';
import { DetailsScreen } from './DetailsScreen';
const Stack = createNativeStackNavigator();
function TestApp() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
describe('Navigation Flow', () => {
it('navigates from home to details', async () => {
render(<TestApp />);
// On Home screen
expect(screen.getByText('Home Screen')).toBeTruthy();
// Navigate to Details
const item = screen.getByText('View Details');
fireEvent.press(item);
// Wait for Details screen
await waitFor(() => {
expect(screen.getByText('Details Screen')).toBeTruthy();
});
});
});
describe('ContactForm', () => {
it('validates all fields before submit', async () => {
const onSubmit = jest.fn();
render(<ContactForm onSubmit={onSubmit} />);
const submitButton = screen.getByText('Submit');
fireEvent.press(submitButton);
await waitFor(() => {
expect(screen.getByText('Name is required')).toBeTruthy();
expect(screen.getByText('Email is required')).toBeTruthy();
expect(onSubmit).not.toHaveBeenCalled();
});
});
});
describe('ItemsList', () => {
it('renders all items', () => {
const items = [
{ id: '1', title: 'Item 1' },
{ id: '2', title: 'Item 2' },
{ id: '3', title: 'Item 3' },
];
render(<ItemsList items={items} />);
items.forEach(item => {
expect(screen.getByText(item.title)).toBeTruthy();
});
});
it('handles empty state', () => {
render(<ItemsList items={[]} />);
expect(screen.getByText('No items found')).toBeTruthy();
});
});
describe('Button accessibility', () => {
it('has correct accessibility props', () => {
render(<Button title="Submit" onPress={jest.fn()} />);
const button = screen.getByRole('button');
expect(button).toHaveAccessibilityState({ disabled: false });
expect(button).toHaveAccessibilityHint('Submits the form');
});
it('is disabled when loading', () => {
render(<Button title="Submit" onPress={jest.fn()} loading />);
const button = screen.getByRole('button');
expect(button).toHaveAccessibilityState({ disabled: true, busy: true });
});
});
❌ Don't test implementation details:
// Bad - testing internal state
expect(component.state.count).toBe(5);
// Good - test observable behavior
expect(screen.getByText('Count: 5')).toBeTruthy();
❌ Don't use querySelector or DOM methods:
// Bad
const element = container.querySelector('.button');
// Good
const button = screen.getByRole('button');
❌ Don't create overly coupled tests:
// Bad - too specific
expect(screen.getByText('Submit')).toHaveStyle({
backgroundColor: '#007AFF',
paddingHorizontal: 16
});
// Good - test behavior
const button = screen.getByText('Submit');
expect(button).toBeTruthy();
fireEvent.press(button);
expect(mockSubmit).toHaveBeenCalled();
❌ Don't forget to clean up:
// Bad
afterEach(() => {
// No cleanup
});
// Good
afterEach(() => {
jest.clearAllMocks();
cleanup();
});
npx claudepluginhub thebushidocollective/han --plugin react-native-webTests React Native apps with Jest, React Native Testing Library, and Detox for unit, integration, and E2E coverage. Covers mocking native modules, navigation, and async storage.
Writes, reviews, and fixes React Native component tests using @testing-library/react-native v13 (sync) and v14 (async). Covers render, queries, userEvent, fireEvent, waitFor, Jest matchers, and Expo setup.
Tests React components with React Testing Library, Vitest/Jest, MSW for network mocking, and axe accessibility assertions. Guides test vs E2E decisions.