Behavior of Hermes Builds When Mocking Native Modules in React Native Integration Tests
0 reputation · 29 Jan 2023, 07:38 UTC
0 reputation · 29 Jan 2023, 07:38 UTC
Goal: Ensure that integration tests using mocked environment variables produce identical outcomes regardless of whether the Hermes engine is enabled in debug or release build configurations.
Constraint: Community discussion notes that Hermes may treat mocked native modules differently during JSI module initialization, leading to inconsistent test behavior between builds, yet no definitive guidance exists on aligning test environments or configuring mocks to guarantee parity.
Does Hermes initialize mocked native modules identically in debug and release modes? What test‑setup adjustments eliminate JSI‑related discrepancies? Are there recommended mocking patterns that work uniformly across both Hermes configurations?
32775 reputation · 29 Jan 2023, 16:21 UTC
Hermes does not guarantee identical initialization of mocked native modules between debug and release builds. The likely explanation is that release builds use pre-compiled Hermes bytecode, which can evaluate modules earlier than the debug JIT path — potentially before your JS-level mock is applied. The fix is to apply mocks before any module evaluation and, when the module uses JSI host objects, to mock the native binding as well.
Confirmed: Hermes communicates with native modules via the JavaScript Interface (JSI), bypassing the asynchronous bridge. Mocking at the JavaScript layer (e.g., jest.mock) is engine-agnostic — the same mock code runs regardless of Hermes configuration. If a native module exposes JSI host objects, those bindings are created on the native side and cannot be intercepted by a JS-only mock unless the module is fully decoupled.
Likely (not definitively confirmed): The debug/release discrepancy comes from bytecode pre-compilation in release builds altering module evaluation timing, plus differences in memory management that can create race conditions during mock injection. This matches community reports but should be verified in your own setup.
jest.mock('path/to/module') at the very top of your test file or in a global setup file referenced by setupFiles in your Jest config.jest.mock('react-native-blur', () => ({
BlurView: () => null,
// if the module exports a host object, mock it as a plain object
NativeBlurHost: {},
}));__mocks__/<moduleName>.js returning an object matching the expected native interface, or by stubbing through NativeModules:import { NativeModules } from 'react-native';
NativeModules.MyModule = { getEnv: () => 'test' };const mod = require('react-native-blur');
console.log(mod.NativeBlurHost); // should be the mock, not the native hostThe pattern that works uniformly across both Hermes configurations is: intercept the module export in a global setup file, mock the full export surface (including any host objects as plain objects), and avoid importing the real module anywhere in test code paths. This guarantees the mock is in place before Hermes executes the bundled code, regardless of whether that code is JIT-compiled (debug) or pre-compiled bytecode (release).
Does the mocked native module expose JSI host objects or C++-bound methods, or is it a pure JavaScript wrapper? If pure JS, the JS-level mock alone guarantees parity. If it uses JSI, you must also mock the native bindings as shown above — otherwise release builds may initialize the real host object before your mock takes effect.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.