Run your React Native component tests in Vitest — no emulator or device required.
Attribution: This project is based on the original work by Vladimir Sheremet at sheremet-va/vitest-react-native. Thank you for creating the foundation that made this possible!
- Fast — Run tests in Node.js without building native code
- Full React Native API support — Mocks for all core components and APIs
- Tracks React Native's supported releases — see Supported React Native versions
- Works with @testing-library/react-native — Use familiar testing patterns
- TypeScript support — Full type definitions included
npm install -D @srsholmes/vitest-react-native vitest @testing-library/react-native
# or
yarn add -D @srsholmes/vitest-react-native vitest @testing-library/react-native
# or
pnpm add -D @srsholmes/vitest-react-native vitest @testing-library/react-nativeCreate or update your vitest.config.ts:
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
import { reactNative } from '@srsholmes/vitest-react-native';
export default defineConfig({
plugins: [react(), reactNative()],
test: {
globals: true,
environment: 'node',
},
});// Button.test.tsx
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import { Text, Pressable } from 'react-native';
const Button = ({ onPress, title }: { onPress: () => void; title: string }) => (
<Pressable onPress={onPress} testID="button">
<Text>{title}</Text>
</Pressable>
);
describe('Button', () => {
it('calls onPress when pressed', () => {
const onPress = vi.fn();
const { getByTestId } = render(<Button onPress={onPress} title="Click me" />);
fireEvent.press(getByTestId('button'));
expect(onPress).toHaveBeenCalledTimes(1);
});
});npx vitestimport { reactNative } from '@srsholmes/vitest-react-native';
reactNative({
// Add custom file extensions to resolve
additionalExtensions: ['.custom.ts'],
});If you prefer not to use the plugin, you can configure manually:
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
extensions: [
'.ios.ts',
'.ios.tsx',
'.native.ts',
'.native.tsx',
'.ts',
'.tsx',
'.js',
'.jsx',
'.json',
],
conditions: ['react-native'],
},
test: {
setupFiles: ['@srsholmes/vitest-react-native/setup'],
globals: true,
environment: 'node',
server: {
deps: {
external: ['react-native'],
},
},
},
});All core React Native components are mocked and render as testable elements:
| Component | Status |
|---|---|
| View | ✅ |
| Text | ✅ |
| Image | ✅ |
| TextInput | ✅ |
| ScrollView | ✅ |
| FlatList | ✅ |
| SectionList | ✅ |
| VirtualizedList | ✅ |
| Pressable | ✅ |
| TouchableOpacity | ✅ |
| TouchableHighlight | ✅ |
| TouchableWithoutFeedback | ✅ |
| TouchableNativeFeedback | ✅ |
| Modal | ✅ |
| ActivityIndicator | ✅ |
| Button | ✅ |
| Switch | ✅ |
| RefreshControl | ✅ |
| StatusBar | ✅ |
| KeyboardAvoidingView | ✅ |
| SafeAreaView | ✅ |
| ImageBackground | ✅ |
| InputAccessoryView | ✅ |
| DrawerLayoutAndroid | ✅ |
| API | Status | Notes |
|---|---|---|
| Animated | ✅ | Full animation API with timing, spring, etc. |
| StyleSheet | ✅ | create, flatten, compose, etc. |
| Platform | ✅ | OS detection, select, etc. |
| Dimensions | ✅ | get, addEventListener |
| PixelRatio | ✅ | get, getFontScale, etc. |
| Linking | ✅ | openURL, canOpenURL, etc. |
| Alert | ✅ | alert, prompt |
| Share | ✅ | share |
| Keyboard | ✅ | dismiss, addListener, etc. |
| AppState | ✅ | currentState, addEventListener |
| Clipboard | ✅ | getString, setString |
| Vibration | ✅ | vibrate, cancel |
| BackHandler | ✅ | addEventListener, exitApp |
| PermissionsAndroid | ✅ | request, check, requestMultiple |
| ToastAndroid | ✅ | show, showWithGravity |
| ActionSheetIOS | ✅ | showActionSheetWithOptions |
| AccessibilityInfo | ✅ | Full accessibility API |
| Appearance | ✅ | getColorScheme, addChangeListener |
| LayoutAnimation | ✅ | configureNext, create, Presets |
| InteractionManager | ✅ | runAfterInteractions (removed in RN 0.87) |
| PanResponder | ✅ | create |
| NativeEventEmitter | ✅ | addListener, removeAllListeners |
| Hook | Status |
|---|---|
| useWindowDimensions | ✅ |
| useColorScheme | ✅ |
import { render, waitFor } from '@testing-library/react-native';
it('loads data asynchronously', async () => {
const { getByText } = render(<DataComponent />);
await waitFor(() => {
expect(getByText('Loaded')).toBeTruthy();
});
});import { render, fireEvent } from '@testing-library/react-native';
it('updates text on input change', () => {
const { getByTestId } = render(<Form />);
const input = getByTestId('name-input');
fireEvent.changeText(input, 'John Doe');
expect(input.props.value).toBe('John Doe');
});it('matches snapshot', () => {
const { toJSON } = render(<MyComponent />);
expect(toJSON()).toMatchSnapshot();
});This package follows React Native's own support policy: it supports the releases React Native marks as Active or End of Cycle. When React Native moves a release to Unsupported, a later minor release of this package may drop it.
@srsholmes/vitest-react-native |
React Native |
|---|---|
| 0.2.x | 0.85 – 0.87 |
| 0.1.x | 0.79 – 0.84 |
On an older React Native, stay on the matching version of this package (for example npm install -D @srsholmes/vitest-react-native@0.1).
While this package is below 1.0, a minor bump (0.1 → 0.2) signals breaking changes, such as dropping React Native versions; patch releases are always compatible. ^0.1.x will never install 0.2.
Mocks follow the installed React Native version: APIs React Native has removed (for example InteractionManager and the StatusBar.setBackgroundColor / setTranslucent / setNetworkActivityIndicatorVisible setters in 0.87) are not mocked on versions where they no longer exist, so tests fail the same way the app would.
- Node.js 20.19.4+ (React Native 0.87 itself requires Node.js 22.13+)
- React 19.2.3+
- React Native — see Supported React Native versions
- Vitest 4+
- Vite 6+
Ensure react-native is in your devDependencies and the plugin is properly configured.
The setup file automatically strips Flow types from React Native source files. If you encounter issues, ensure the setup file is loaded first in your setupFiles array.
Make sure you're using @testing-library/react-native for rendering, not @testing-library/react.
Contributions are welcome! Please feel free to submit a Pull Request.
# Clone the repo
git clone https://github.com/srsholmes/vitest-react-native.git
# Install dependencies
pnpm install
# Run tests
pnpm test
# Run linting
pnpm lint
# Format code
pnpm formatMIT
- Original implementation by Vladimir Sheremet — sheremet-va/vitest-react-native
- Vitest team for the amazing test framework
- React Native team for the mobile framework