Why does React Native's Jest preset run tests in its Node-based react-native-env instead of jsdom, and what does that change?
answer
- no DOM on a phone
- extends jest-environment-node
- export conditions: require, react-native
- window is the Node global
- Node's engine, not Hermes
basics
~20 sReact Native renders native views, not DOM nodes, so a browser DOM would give tests APIs the app never has. The preset's react-native-env extends Jest's Node environment and resolves packages with the react-native export condition.
solid answer
~40 sA React Native app has no `document`, no DOM elements and no browser globals, so jsdom would only let tests pass against APIs that do not exist on the phone and could make libraries pick their web builds. The preset's `testEnvironment` is `react-native-env`, a class that extends `jest-environment-node` and sets `customExportConditions` to `['require', 'react-native']`, so packages with an `exports` map resolve their React Native entry. The preset's setup file then fills in the few globals React Native code expects: `window` as an alias of the Node global, `requestAnimationFrame` on top of `setTimeout`, `performance.now`, `__DEV__`. Tests still execute on Node's JavaScript engine rather than Hermes, and components render to a JavaScript tree rather than native views.
code
javascript · 6 lines// @react-native/jest-preset/jest/react-native-env.js (0.87.1)
const NodeEnv = require('jest-environment-node').TestEnvironment;
module.exports = class ReactNativeEnv extends NodeEnv {
customExportConditions = ['require', 'react-native'];
};go deeper
Recall that React Native has no DOM, so its Jest preset uses a Node-based environment rather than jsdom.
Explain what react-native-env adds: Node globals, the react-native export condition, and the preset's extra globals such as window and requestAnimationFrame.
Anticipate where the environment misleads: browser-detection checks that see window, Hermes-only behaviour, and layout-dependent logic that silently passes in Node.
Use the environment's known blind spots to decide which behaviours a team must verify on devices rather than trust to Node-side tests.
## What a Jest test environment is Jest runs each test file inside a **test environment**: an object that creates the global scope the test sees. Jest ships a Node environment (plain Node globals) and a jsdom environment (a simulated browser with `document`, `window`, DOM elements and events). Web React projects typically use jsdom because React DOM needs a document to render into. ## Why React Native does not use jsdom React Native does not render to a DOM. Its components (`View`, `Text`, `Image`) become **native views** on iOS and Android, and in a Node test they render to a tree of plain JavaScript objects through a test renderer. React Native Testing Library's documentation makes the same point: web testing can rely on jsdom, but there is no equivalent package that simulates the React Native runtime. Using jsdom anyway would cause three problems: - Tests could call `document`, `localStorage` or DOM events and pass, even though none of these exist in the app. - Libraries that detect a browser could take their web code path, so the test would exercise different code from the app. - Packages with an `exports` map could resolve their `browser` build instead of their React Native build. ## What react-native-env actually is The preset's `testEnvironment` points at `jest/react-native-env.js`. It is short: - it extends `TestEnvironment` from `jest-environment-node`, so the globals are Node's; - it sets **`customExportConditions`** to `['require', 'react-native']`. Export conditions matter because modern packages declare an `exports` map in `package.json` with entries per condition (`react-native`, `browser`, `import`, `require`, `default`). Listing `react-native` makes Jest pick the same entry Metro picks when it bundles for a phone. The preset also installs a custom `resolver` that removes the `exports` field of the `react-native` package itself while resolving, a backwards-compatibility measure so tests can still resolve and mock React Native's internal subpaths. ## Globals the preset adds on top of Node Because the environment is plain Node, the preset's setup file defines the handful of globals React Native code expects: | Global | Value under the preset | |---|---| | `__DEV__` | `true` | | `window` | the Node global object itself | | `requestAnimationFrame` | a `setTimeout` with a 0 ms delay that calls back with `jest.now()` | | `cancelAnimationFrame` | `clearTimeout` | | `performance.now` | a Jest mock of `Date.now` | | `nativeFabricUIManager` | an empty object | None of this makes Node look like a phone; it only supplies the names React Native's JavaScript reads during import and render, so that loading `react-native` in a test does not crash before the first assertion. Two consequences follow. Code that checks `typeof window !== 'undefined'` to detect a browser gets a false positive, because `window` exists. And animation-frame callbacks are driven by timers, so they respond to Jest's timer controls like any `setTimeout`. ## What the Node environment cannot give you 1. **The engine is Node's, not Hermes.** Tests execute on the JavaScript engine that ships with Node. A behaviour specific to Hermes, such as a missing or different built-in, will not reproduce. 2. **No native views.** There is no layout engine, so sizes, positions and scrolling are not computed; `onLayout` does not fire with real measurements. 3. **Native modules are mocks.** Anything that crosses into native code is whatever mock the preset or your setup registered. ## Comparing the two environments | Question | jsdom | react-native-env | |---|---|---| | Is there a `document`? | Yes | No | | Which package entry resolves? | Browser-oriented conditions | `require` and `react-native` | | What do components render to? | DOM nodes | JavaScript objects via a test renderer | | Does it match the phone's globals? | No, it adds browser APIs | Closer: Node plus a few React Native globals | The short interview answer: React Native has no DOM, so the preset uses a Node environment tuned with React Native export conditions and globals, and anything that needs the real engine, layout or native code belongs in a device-level test.
- A shared utility checks typeof window !== 'undefined' to decide it is on the web; what happens in a React Native Jest test?The preset's setup file defines `window` as an alias of the Node global, so the check is true and the utility takes its web branch in tests even though the app never does. Detect React Native with `Platform.OS` or a platform-specific file instead of probing browser globals.
- Why can a test pass in Jest yet the same code throw on the phone?Tests run on Node's JavaScript engine with mocked native modules and no real layout. Code that depends on a Hermes-specific built-in, a real native module response or measured layout is not exercised, so those failures only appear on a device or simulator.
saying these in an interview costs you the question
- React Native tests should use jsdom because React renders to a DOM
- The preset's environment runs the Hermes engine inside Jest
- window is undefined under React Native's Jest preset
- react-native-env computes real layout for View components
- Export conditions do not affect which file a package resolves to