Upgrading to 0.15
0.15 raises the supported versions and changes what some suites see. Start with doctor, which checks your versions and your config:
npx vitest-native doctorComing from 0.9 or older? Start with Upgrading from 0.9 or older.
Supported versions
| 0.15 needs | An older one | |
|---|---|---|
| Node | 22.22.3+ on Node 22, 24.12+ on Node 24, or 26+ | stops at startup with UNSUPPORTED_NODE |
| Vitest | 5 | stops with UNSUPPORTED_PEER |
| Vite | ^8.0.5 | stops with UNSUPPORTED_PEER |
| React | 19+ | stops with UNSUPPORTED_PEER |
| React Native (native engine) | 0.86–0.88 | below 0.86 stops with UNSUPPORTED_PEER; above 0.88 warns |
| Expo SDK | 57 and 58 | warns |
| React Native Testing Library | 13–14 | warns |
- Update Node in your machine, CI images and
.nvmrcorengines. Avoid Node 24.13.0, which can crash a run at startup (doctorwarns); use another supported Node 24, such as 24.14 or later. The native engine needs Node: Bun stops withUNSUPPORTED_NODE. - Install
vitest@^5.1.0with matching@vitest/*packages, andvite@^8.0.5. Vitest 5.0.x runs, with a startup warning: 5.1.0 or later is recommended. - Upgrade React Native to 0.86 or later (Expo SDK 57 or later) and React to 19, and move from React Native Testing Library 12 to 13 or 14. Testing Library 14 makes
render,fireEventandactasync:await render(<App />). - Vitest 5 clears mock calls before every test. A suite that asserts on calls made before the test started sets
test.clearMocks: false. The plugin also turns on Vitest 5'sfsModuleCache; settest.fsModuleCache: falseto keep it off. - Babel plugins your tests need go through
babel()from@rolldown/plugin-babel:@vitejs/plugin-react6 removed itsbabeloption. - Remove
VITEST_NATIVE_LOADER_THREADfrom scripts and CI. It no longer has an effect.
Re-record snapshots once
Snapshots of React Native trees now print in Jest's format, as Vitest prints any React tree, and no host prints a ref. Snapshots recorded by an earlier vitest-native fail once:
npx vitest run -uReview the diff before committing it. A host ref, such as a Text ref under the native engine or any host ref under the mock engine, is now an instance with measure, focus and the other native methods, where it used to be the test renderer's node or null; update assertions on ref.current.
For one release, the old format is still available, and deprecated: expect.addSnapshotSerializer(serializer) with serializer from vitest-native/serializer, in a setup file. Snapshots has the details, including which Jest-written snapshots need a re-record.
Manual mocks of node_modules packages apply
With jestMockTransform(), a file such as __mocks__/moti.ts in a __mocks__ folder under Jest's roots (by default the Vite root) now replaces that package with no jest.mock() call, as it does under Jest. Before, the real package ran.
If a test relied on the real package, delete or rename that mock, or keep the previous behaviour with jestMockTransform({ rootMocks: false }). A package a preset covers keeps its preset, and the run says the manual mock is not applied. Manual mocks of node_modules packages has the details.
Your Babel config applies where it is needed
A file that needs a plugin from your project's Babel config (legacy decorators, macros, react-native-dotenv, the worklets plugin, custom plugins, Flow) now compiles with that config, as under Metro and babel-jest. Every other file keeps Vite's own compiler.
- Remove any
babel()entry you added only to run those plugins.doctornames the ones that are now redundant. @babel/coremust be installed in the project.migrate,doctorand the mock engine's startup say when it is not.- To keep the previous behaviour, set
projectBabel: 'never'.
Gesture Handler and FlashList run as themselves
Under the native engine, react-native-gesture-handler and @shopify/flash-list now run as their installed packages, with the Jest setup each ships, as a Jest suite runs them.
react-native-gesture-handler/Swipeableand the other modules Gesture Handler 3 does not ship no longer resolve.- A
FlashListrenders a window of rows, as under Jest, where it used to render every row. - To keep a preset, set
presets: { gestureHandler: true }orpresets: { flashList: true }. - A
presets: { gestureHandler: false }carried over from older mocks can go: it no longer turns off Gesture Handler's own Jest setup, andflashList: falselikewise.
Other libraries' own mocks
Under the native engine, the worklets, safe-area-context, keyboard-controller, bottom-sheet and netinfo presets are now each library's own Jest mock, and Reanimated 4.7+ is its own mock. Snapshots of their output may change; review the diff and re-record with vitest -u.
- Safe-area insets are zero unless
<SafeAreaProvider initialMetrics>sets them. A test that expects other insets passesinitialMetrics, or callssetInsets(), which holds untilresetAllMocks(). - Worklets'
runOnJSandscheduleOnRNrun a tick later, as under Jest: wait for their effect (await waitFor(...)), or advance fake timers. - Implementations a library sets up in its own mocks survive
vi.resetAllMocks()andmockReset: true. Update a test that expected them to be blank after a reset. - Reanimated's
interpolate,interpolateColorandclampreturn real values, so snapshots of interpolated styles may change.
Workarounds you can delete
vi.stubEnv('RNTL_SKIP_AUTO_CLEANUP', 'true')added for the hot runtime:@testing-library/react-native/dont-cleanup-after-eachnow works in every file.- A mock of a
react-native/Libraries/...file's.ios.jscopy: ajest.mockof the plain path now reaches the platform file. - Renames to
.jsx, oroxc.includeoverrides, added so your own.jsfiles with JSX compile: they now compile as under Metro and babel-jest.
Also new
- The hot runtime fails a stuck run instead of waiting forever, naming the file and test. Set
hangTimeoutto change or turn this off. - A factory-less
jest.mock('pkg')automockspkgforrequire()too, as under Jest, for an installed package with no manual mock. A test that relied onrequiregetting the real package givesjest.mocka factory. SOURCE_EVALUATED_TWICEis a new warning under the native engine, naming a file Vite and Node both load; its message gives the fix. SetreactNative({ ownership: 'error' })to fail on it.init,doctorandmigrateexit 1 on a flag they do not take, instead of ignoring it. Check scripts that pass extra flags to them.
Upgrading from 0.9 or older
Go straight to 0.15; there is no need to stop at each release in between. The config keeps its shape: reactNative() from vitest-native and the helpers in vitest-native/helpers have the same names.
- Raise the versions in Supported versions. 0.9 accepted Node 20, Vitest 4, Vite 6, React 18, React Native 0.81 and React Native Testing Library 12, none of which 0.15 runs on. Check every row.
- Install
vitest-native@^0.15and runnpx vitest-native doctor. - Run the suite, then re-record snapshots once with
npx vitest run -uand review the diff. - Work through the failures with the sections above and the list below: the changes since 0.9 that most often need an edit.
Under the native engine:
- Native modules answer as on a device.
NativeModules[name]isundefinedandTurboModuleRegistry.get(name)isnullfor a module no app registers, and a React Native module's stub has only the methods its codegen spec declares. Register what a test needs withmockNativeModule(name, impl), or setnativeModules: 'permissive'while you add them. - Expo modules run as their installed packages, with jest-expo's values:
Constants.expoConfigis{},Linking.createURL()needs a scheme mocked intoexpo-constants, andFont.isLoaded()isfalseuntil a font loads.presets: { expo: true }keeps the preset while a suite moves off it. require('./logo.png')gives the asset's registered id, as with Metro, not its file name.
With either engine:
- Presets no longer export names the real package lacks. Read
expoConfigfromexpo-constants' default export, andAnimated.Viewfrom Reanimated's default. - A
presetsarray turns off auto-detection, and under the native engine Gesture Handler's and FlashList's own Jest setup with it. To turn off one preset, usepresets: { <name>: false }. - A preset of your own is listed by its path, as in
presets: { './test/presets/my-lib.ts': true }; a preset object passed in is refused. - Unistyles and Skia are mocked automatically. Remove the
react-native-unistyles/mockssetup file and any hand-written Skia mock. hotRuntime: truefails at startup withHOT_RUNTIME_OVERRIDDENwhen a pool,test.isolateor a CLI flag conflicts with it, instead of overriding them.
The changelog has the rest: each release's Minor Changes from 0.10 on, "Upgrading from 0.13" in 0.14.0 and "Upgrading from 0.14" in 0.15.0.
From 0.3 or older
0.3 ran the mock engine by default, a pure-JavaScript stand-in for React Native. Since 0.4, reactNative() runs React Native's own JavaScript whenever react-native, its Babel preset (or Expo's) and @babel/core are installed, as they are in a React Native or Expo app. Expect more tests to change than from 0.9.
- To move a suite across in steps, set
engine: 'mock'to keep the mock engine, then remove it once the suite passes under the native engine. Under the mock engine,useAnimatedValue,useAnimatedValueXYanduseAnimatedColorare now called inside a component, as in React Native. - The
mocksoption applies to the mock engine only; the native engine stops with a configuration error when it is set. Usevi.mock()in a setup file ormockNativeModuleinstead.
The full list of changes is in the changelog.