skip to content

Mocking Platform Modules

Native modules don't exist under Node, so tests mock them: the preset's built-in mocks, library-shipped mocks, and Platform.OS defaulting to ios. Interviewers probe the missing-native-module error.

part ofReact Nativeoverview, primer and where to startread it →
on this pageshow

explore

questions

4

In React Native Jest tests, why does a screen using a custom location TurboModule fail on import, and how do you mock it?

level: middleimportance: must knowfreq 55%

answer

  1. no native binary under Node
  2. getEnforcing throws at import time
  3. falls back to the preset's NativeModules mock
  4. jest.mock the spec module
  5. cover the denied and error paths

basics

~20 s

Under Jest there is no native binary, so TurboModuleRegistry.getEnforcing cannot find the location module and throws when the spec file is imported. Mock that spec module (or your wrapper around it) with jest.mock and a factory of jest.fn methods.

solid answer

~40 s

A TurboModule spec usually ends with `export default TurboModuleRegistry.getEnforcing<Spec>('NativeLocation')`, which runs when the file is imported. On a device the TurboModule proxy returns the native implementation; in Jest there is no proxy, so the registry falls back to the `NativeModules` object, which is the preset's mock and knows only core modules. `getEnforcing` then throws `'NativeLocation' could not be found`, and the test fails before rendering. The fix is to `jest.mock` the module that calls `getEnforcing`, the spec file or the hook that wraps it, with a factory returning `jest.fn()` methods, then set per-test results such as a resolved position or a rejected permission. Mock at the boundary your code owns rather than patching React Native's internals.

code

typescript · 9 lines
typescript
// src/specs/NativeLocation.ts
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  getCurrentPosition(): Promise<{ latitude: number; longitude: number }>;
}

export default TurboModuleRegistry.getEnforcing<Spec>('NativeLocation');

go deeper

for a junior

Recall that native modules do not exist when Jest runs in Node, so any TurboModule your code imports must be replaced with a mock.

for a middle

Explain why getEnforcing throws at import time, why the preset's NativeModules mock cannot help, and how jest.mock on the spec or wrapper fixes it.

for a senior

Design mocks that exercise real branches such as denied permission and slow responses, place shared mocks deliberately, and name what only a device test can prove.

for a principal

Push native access behind thin, owned wrappers so the test suite mocks one stable boundary instead of many module names that drift with native code.

## What fails, and when A store-finder screen reads the device position through a custom **TurboModule** named `NativeLocation`. Its JavaScript spec follows the New Architecture pattern: ```ts export default TurboModuleRegistry.getEnforcing<Spec>('NativeLocation'); ``` That line is a **top-level statement**. It runs the moment any file imports the spec, directly or through the screen. In a Jest test the import chain is evaluated while the test file loads, so the failure appears before `render` is ever called: `TurboModuleRegistry.getEnforcing(...): 'NativeLocation' could not be found. Verify that a module by this name is registered in the native binary.` ## Why the module is missing under Jest `TurboModuleRegistry` looks for a module in two places: 1. the **TurboModule proxy** installed by the native runtime, which exists only inside a real app; 2. the `NativeModules` object, the legacy lookup path. In Jest there is no native runtime, so step 1 finds nothing. For step 2, `@react-native/jest-preset` replaces `NativeModules` with a mock object that contains fake implementations of **core** modules only (for example `PlatformConstants`, `DeviceInfo`, `NativeAnimatedModule`, `Networking`). A module from your app or from a third-party library is not in it, so `getEnforcing` throws. The non-throwing `TurboModuleRegistry.get` would return `null` instead, and the failure would move to the first method call. ## The fix: mock the boundary you own The robust fix is to replace the **module that calls `getEnforcing`** with a test double, so the real lookup never runs: - **Mock the spec file** when components call it directly. `jest.mock('../src/specs/NativeLocation', factory)` is hoisted above the imports, so the screen receives the mock. - **Mock your wrapper** (for example a `useCurrentPosition` hook or a `locationService` module) when the app already isolates native access. This keeps tests independent of the spec's method names. - **Register it once** in a file listed in `setupFiles` when many suites import the module; mock per file when each suite needs different behaviour. | Approach | Good for | Cost | |---|---|---| | `jest.mock` of the spec file | Screens that call the module directly | Tests know the native method names | | `jest.mock` of a wrapper hook or service | Apps that funnel native access through one module | One more layer to keep thin | | Global mock in a `setupFiles` script | Many suites needing the same default | Hidden defaults can surprise a reader | | Patching `NativeModules` or React Native internals | Almost never | Couples tests to private React Native paths | ## Making the mock useful A mock that only stops the crash tests nothing. For a location module, drive each branch the screen has: - a resolved position, to check the nearby-stores list renders; - a rejected call with a permission error, to check the "Location unavailable" state; - a slow call, to check the loading indicator, using a promise the test resolves later. Use `jest.mocked(...)` to get typed access to the mocked methods and set results per test, and reset them between tests so one test's position does not leak into the next. ## What the mock cannot tell you A Jest mock proves the **JavaScript contract**: the screen calls the right method and handles each outcome. It cannot prove that the native implementation exists, is registered under the same name, or returns the shape the spec promises. Those belong to a build and a device-level test. A typo between `'NativeLocation'` in the spec and the native registration passes every Jest test and fails on the phone. ## Keeping mocks honest over time A mock is a second copy of the module's contract, and copies drift. Three habits keep it aligned: - type the mock from the spec (`jest.mocked` over the real import) so a renamed method fails to compile in the test; - keep one shared default mock rather than a different inline factory in every suite; - when the native side changes a method, update the spec, the wrapper and the default mock in the same change. ## Third-party libraries Libraries follow the same pattern: many define a spec that calls `getEnforcing` with their own module name, so importing them in Jest throws the same error until they are mocked. Before writing a mock by hand, check whether the library ships one, as NetInfo and AsyncStorage do; a shipped mock tracks the library's API across versions.

  • Why does the error appear even in a test that never touches the location feature?
    The spec's `getEnforcing` call runs at import time. If any module in the test's import graph imports the spec, even indirectly through a barrel file or a navigator that registers the store-finder screen, the lookup runs and throws. Mocking the spec in a `setupFiles` script, or breaking the import chain, fixes it for those suites.
  • Would switching the spec to TurboModuleRegistry.get avoid the need for a mock?
    It only moves the failure. `get` returns `null` when the module is missing, so the import succeeds but the first call dereferences `null`. It also weakens the app: a missing native registration is no longer reported at startup. Keep `getEnforcing` for required modules and mock them in tests.
  • Where should a mock live if twenty suites import screens that use the location module?
    Register a default mock once in a file listed in Jest's `setupFiles`, so every suite gets it before its imports run, and let individual tests override the method results. Keep the default neutral, for example a fixed position, so a suite that forgets to configure it still behaves predictably.

saying these in an interview costs you the question

  • The preset's NativeModules mock includes every installed native module
  • The error only happens when the test calls the location method
  • Adding the module to transformIgnorePatterns makes it available in Jest
  • A passing Jest mock proves the native module is registered correctly
  • Patching react-native internals is the cleanest way to add a module
open as a page

In React Native Jest tests, what is Platform.OS by default, and how do you test a component's Android branch?

level: juniorimportance: should knowfreq 38%

basics

~20 s

Platform.OS is 'ios' in tests because the React Native preset's haste defaultPlatform is 'ios', so Platform.ios.js and .ios files load. Test Android branches with a second Jest project whose defaultPlatform is 'android', or by injecting the value.

open as a page

In a React Native Jest suite, how do you wire the mocks that AsyncStorage, NetInfo and Reanimated ship instead of writing your own?

level: middleimportance: should knowfreq 42%

basics

~10 s

Each library ships its own Jest integration: AsyncStorage's in-memory mock at @react-native-async-storage/async-storage/jest, NetInfo's jest/netinfo-mock.js, and Reanimated's Jest resolver plus setUpTests() in a setup file.

open as a page

A React Native Jest suite with Animated transitions flakes in CI and logs act() warnings after tests end; what causes it, and how do fake timers fix it?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Animated animations keep scheduling frames on real timers after a test finishes, so callbacks and state updates land late and timing depends on CI speed. Fake timers make the animation advance only when the test calls jest.advanceTimersByTime.

open as a page