| name | react-native-testing |
| description | Generate and write tests for React Native applications using React Native Testing Library (RNTL), Jest, and userEvent. Use this skill when the user asks to write tests, create test files, add unit tests, add component tests, or generate test suites for React Native or Expo projects. Also use when working with .test.tsx files, jest.config.js, or when the user mentions testing React Native components, screens, hooks, or forms. Covers getByRole, getByText, getByLabelText queries, userEvent.press, userEvent.type interactions, waitFor, findBy async patterns, and toBeOnTheScreen matchers. Use when this capability is needed. |
| metadata | {"author":"fontezbrooks"} |
React Native Testing
Complete toolkit for testing React Native applications with React Native Testing Library (RNTL), Jest, and modern testing best practices.
Quick Start
Main Capabilities
This skill provides three core capabilities through automated scripts:
node scripts/component-test-generator.js [component-path] [options]
node scripts/coverage-analyzer.js [project-path] [options]
node scripts/test-suite-scaffolder.js [project-path] [options]
Core Concepts
Query Priority (Most Accessible First)
React Native Testing Library promotes testing from the user's perspective. Use queries in this order:
*ByRole - Best for accessibility (buttons, headings, switches)
*ByLabelText - For form inputs with labels
*ByPlaceholderText - For inputs with placeholders
*ByText - For visible text content
*ByDisplayValue - For current input values
*ByHintText - For accessibility hints
*ByTestId - Last resort escape hatch
Query Variants
| Variant | Single | Multiple | Use Case |
|---|
getBy* | getByText | getAllByText | Element MUST exist (sync) |
queryBy* | queryByText | queryAllByText | Element may NOT exist |
findBy* | findByText | findAllByText | Element appears ASYNC |
Decision Guide:
getBy*: "I know this element exists right now"
queryBy*: "This element might not exist, and that's okay"
findBy*: "This element will exist soon (after async operation)"
Core Testing Patterns
1. Basic Component Testing
import { render, screen } from '@testing-library/react-native';
describe('MyComponent', () => {
it('renders correctly', () => {
render(<MyComponent />);
expect(screen.getByRole('header', { name: 'Welcome' })).toBeOnTheScreen();
expect(screen.getByText('Hello, World!')).toBeOnTheScreen();
});
it('renders with props', () => {
render(<MyComponent name="John" />);
expect(screen.getByText('Hello, John!')).toBeOnTheScreen();
});
});
2. User Interaction Testing
import { render, screen, userEvent } from '@testing-library/react-native';
test('user can interact with form', async () => {
const user = userEvent.setup();
render(<LoginForm />);
await user.type(screen.getByLabelText('Username'), 'admin');
await user.type(screen.getByLabelText('Password'), 'password123');
await user.press(screen.getByRole('button', { name: 'Sign In' }));
expect(await screen.findByRole('header', { name: 'Welcome admin!' })).toBeOnTheScreen();
});
3. Async Operations Testing
import { render, screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react-native';
test('handles async data loading', async () => {
render(<DataComponent />);
await waitForElementToBeRemoved(() => screen.getByText('Loading...'));
expect(await screen.findByText('Data loaded!')).toBeOnTheScreen();
});
test('handles async with waitFor', async () => {
render(<AsyncComponent />);
await waitFor(() => {
expect(screen.getByText('Ready')).toBeOnTheScreen();
});
});
4. Testing Element Absence
test('element is not rendered', () => {
render(<ConditionalComponent showExtra={false} />);
expect(screen.queryByText('Extra Content')).not.toBeOnTheScreen();
expect(screen.queryByTestId('hidden-element')).toBeNull();
});
5. Testing Form Inputs
test('form input interactions', async () => {
const user = userEvent.setup();
render(<FormComponent />);
const input = screen.getByLabelText('Email');
await user.type(input, 'test@example.com');
expect(input).toHaveDisplayValue('test@example.com');
await user.clear(input);
await user.type(input, 'new@example.com');
});
6. Testing Lists and Multiple Elements
test('renders list items', () => {
render(<ItemList items={['Item 1', 'Item 2', 'Item 3']} />);
const items = screen.getAllByRole('listitem');
expect(items).toHaveLength(3);
expect(screen.getByText('Item 1')).toBeOnTheScreen();
expect(screen.getByText('Item 2')).toBeOnTheScreen();
});
Jest Matchers Reference
Element Presence
expect(element).toBeOnTheScreen();
expect(element).not.toBeOnTheScreen();
Text Content
expect(element).toHaveTextContent('Hello World');
expect(element).toHaveTextContent(/hello/i);
expect(element).toHaveTextContent('Hello', { exact: false });
Form Values
expect(input).toHaveDisplayValue('expected value');
expect(slider).toHaveAccessibilityValue({ now: 50, min: 0, max: 100 });
Element Properties
expect(button).toBeEnabled();
expect(button).toBeDisabled();
expect(element).toBeVisible();
expect(element).toHaveStyle({ backgroundColor: 'red' });
expect(parent).toContainElement(child);
expect(container).toBeEmptyElement();
Accessibility Properties
expect(checkbox).toHaveAccessibilityState({ checked: true });
expect(element).toBeBusy();
expect(accordion).toBeExpanded();
expect(accordion).toBeCollapsed();
expect(tab).toBeSelected();
User Event Methods
Setup and Basic Usage
import { userEvent } from '@testing-library/react-native';
test('user events', async () => {
const user = userEvent.setup();
render(<Component />);
});
Available Methods
await user.press(element);
await user.longPress(element, { duration: 500 });
await user.type(input, 'Hello World');
await user.clear(input);
await user.scrollTo(scrollView, { y: 100 });
await user.focus(input);
await user.blur(input);
Testing Patterns by Component Type
1. Navigation Components
test('navigation flow', async () => {
const user = userEvent.setup();
render(<App />);
await user.press(screen.getByRole('button', { name: 'Go to Details' }));
expect(await screen.findByRole('header', { name: 'Details' })).toBeOnTheScreen();
});
2. Modal/Dialog Components
test('modal opens and closes', async () => {
const user = userEvent.setup();
render(<ModalComponent />);
await user.press(screen.getByRole('button', { name: 'Open Modal' }));
expect(await screen.findByRole('dialog')).toBeOnTheScreen();
expect(screen.getByText('Modal Content')).toBeOnTheScreen();
await user.press(screen.getByRole('button', { name: 'Close' }));
expect(screen.queryByRole('dialog')).not.toBeOnTheScreen();
});
3. List Components (FlatList, SectionList)
test('flatlist renders and scrolls', async () => {
const user = userEvent.setup();
render(<ItemList items={generateItems(50)} />);
expect(screen.getByText('Item 1')).toBeOnTheScreen();
const list = screen.getByTestId('item-list');
await user.scrollTo(list, { y: 1000 });
expect(await screen.findByText('Item 20')).toBeOnTheScreen();
});
4. Form Validation
test('shows validation errors', async () => {
const user = userEvent.setup();
render(<RegistrationForm />);
await user.press(screen.getByRole('button', { name: 'Submit' }));
expect(await screen.findByRole('alert')).toHaveTextContent('Email is required');
await user.type(screen.getByLabelText('Email'), 'invalid-email');
await user.press(screen.getByRole('button', { name: 'Submit' }));
expect(await screen.findByRole('alert')).toHaveTextContent('Invalid email format');
});
5. Async Data Fetching
import { server } from './mocks/server';
import { rest } from 'msw';
test('handles successful data fetch', async () => {
render(<UserProfile userId="123" />);
await waitForElementToBeRemoved(() => screen.getByText(/loading/i));
expect(await screen.findByText('Name: John Doe')).toBeOnTheScreen();
expect(await screen.findByText('Email: john@example.com')).toBeOnTheScreen();
});
test('handles fetch error', async () => {
server.use(
rest.get('/api/user/:id', (req, res, ctx) => {
return res(ctx.status(500), ctx.json({ error: 'Server error' }));
})
);
render(<UserProfile userId="123" />);
( screen.()).();
});
Test Setup & Configuration
Jest Configuration
module.exports = {
preset: 'react-native',
setupFilesAfterEnv: ['<rootDir>/jest-setup.ts'],
transformIgnorePatterns: [
'node_modules/(?!(react-native|@react-native|@testing-library)/)',
],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
collectCoverageFrom: [
'src/**/*.{ts,tsx}',
'!src/**/*.d.ts',
'!src/**/*.test.{ts,tsx}',
],
};
Jest Setup File
import '@testing-library/react-native/extend-expect';
jest.spyOn(console, 'warn').mockImplementation(() => {});
jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper');
import { server } from './mocks/server';
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
Custom Render with Providers
import { render, RenderOptions } from '@testing-library/react-native';
import { ThemeProvider } from './providers/theme';
import { AuthProvider } from './providers/auth';
interface CustomRenderOptions extends RenderOptions {
theme?: 'light' | 'dark';
user?: User | null;
}
function customRender(
ui: React.ReactElement,
{ theme = 'light', user = null, ...options }: CustomRenderOptions = {}
) {
function Wrapper({ children }: { children: React.ReactNode }) {
return (
<ThemeProvider theme={theme}>
<AuthProvider user={user}>
{children}
</AuthProvider>
</ThemeProvider>
);
}
return render(ui, { wrapper: Wrapper, ...options });
}
* ;
{ customRender render };
Anti-Patterns to Avoid
1. Using testID When Accessible Queries Work
expect(screen.getByTestId('submit-btn')).toBeOnTheScreen();
expect(screen.getByRole('button', { name: 'Submit' })).toBeOnTheScreen();
2. Using fireEvent Instead of userEvent
fireEvent.press(button);
fireEvent.changeText(input, 'text');
await user.press(button);
await user.type(input, 'text');
3. Testing Implementation Details
expect(component.state.isLoading).toBe(false);
expect(screen.queryByText('Loading...')).not.toBeOnTheScreen();
4. Using getBy* for Async Content
expect(screen.getByText('Loaded!')).toBeOnTheScreen();
expect(await screen.findByText('Loaded!')).toBeOnTheScreen();
5. Forgetting to Await User Events
user.press(button);
await user.press(button);
Best Practices Summary
Test Organization
- Group related tests with
describe blocks
- Use descriptive test names that explain expected behavior
- Follow Arrange-Act-Assert pattern
- Keep tests focused on single behaviors
Query Selection
- Always prefer accessible queries over testID
- Use semantic queries that match user experience
- Match query variant to test scenario (getBy/queryBy/findBy)
Async Testing
- Always use
findBy* for content that appears asynchronously
- Use
waitFor for complex async conditions
- Use
waitForElementToBeRemoved for loading states
User Interactions
- Always use
userEvent.setup() and await user methods
- Simulate real user flows, not programmatic changes
- Test complete user journeys, not just individual clicks
Performance
- Use
cleanup automatically (or call manually if disabled)
- Mock expensive operations (network, animations)
- Keep tests isolated and independent
Reference Documentation
Query Strategies
Comprehensive guide in references/query_strategies.md:
- Detailed query selection patterns
- Accessibility-first query approaches
- Complex query scenarios
- Performance considerations
Testing Patterns
Complete patterns in references/testing_patterns.md:
- Component testing patterns by type
- Async testing strategies
- State management testing
- Navigation testing
Best Practices
Technical guide in references/best_practices.md:
- Project setup recommendations
- Mock strategies
- CI/CD integration
- Debugging techniques
Common Commands
npm test
npm test -- --watch
npm test -- MyComponent.test.tsx
npm test -- --coverage
npm test -- -u
npm test -- -t "renders correctly"
Tech Stack Compatibility
React Native Versions: 0.73+
Testing Library: @testing-library/react-native 12+
Jest: 29+
TypeScript: 5+
MSW (optional): 2+ for API mocking
Troubleshooting
Common Issues
"Unable to find element"
- Check query is correct (spelling, case sensitivity)
- Use
screen.debug() to see current render tree
- Ensure element has rendered (use findBy* for async)
"Multiple elements found"
- Use more specific query (add name, filter)
- Use
getAllBy* if testing multiple elements
"Act() warnings"
- Wrap state updates in
act()
- Use
waitFor or findBy* for async updates
- Ensure all promises resolve before assertions
"Timeout waiting for element"
- Increase timeout in findBy options
- Check if element is actually rendered
- Verify async operations complete
Getting Help
- Review reference documentation in
references/
- Check React Native Testing Library official docs
- Use
screen.debug() to inspect render output
- Check test script output for detailed errors
Converted and distributed by TomeVault — claim your Tome and manage your conversions.