In React Native Jest tests, why does a screen using a custom location TurboModule fail on import, and how do you mock it?
answer
- no native binary under Node
- getEnforcing throws at import time
- falls back to the preset's NativeModules mock
- jest.mock the spec module
- cover the denied and error paths
basics
~20 sUnder 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 sA 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// 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
Recall that native modules do not exist when Jest runs in Node, so any TurboModule your code imports must be replaced with a mock.
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.
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.
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