Skip to content

Commit 76dcf48

Browse files
authored
refactor: module system patching for module-level mocks (#49)
The module mocking system has been rewritten to improve compatibility with different versions of React Native. Instead of fully overwriting Metro's module system, the new implementation surgically redirects responsibility for imports to Harness, allowing for better integration with various React Native versions while maintaining the same mocking capabilities. The module mocking API has been slightly modified as part of this rewrite.
1 parent 665b055 commit 76dcf48

File tree

14 files changed

+287
-1189
lines changed

14 files changed

+287
-1189
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
default: prerelease
3+
---
4+
5+
The module mocking system has been rewritten to improve compatibility with different versions of React Native. Instead of fully overwriting Metro's module system, the new implementation surgically redirects responsibility for imports to Harness, allowing for better integration with various React Native versions while maintaining the same mocking capabilities. The module mocking API has been slightly modified as part of this rewrite.

apps/playground/src/__tests__/mocking/modules.harness.ts

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,18 @@ import {
77
unmock,
88
requireActual,
99
fn,
10-
clearMocks,
1110
resetModules,
1211
} from 'react-native-harness';
1312

1413
describe('Module mocking', () => {
1514
afterEach(() => {
16-
// Clean up mocks after each test
17-
clearMocks();
15+
resetModules();
16+
});
17+
18+
it('should not interfere with modules that are not mocked', () => {
19+
const moduleA = require('react-native');
20+
const moduleB = require('react-native');
21+
expect(moduleA === moduleB).toBe(true);
1822
});
1923

2024
it('should completely mock a module and return mock implementation', () => {
@@ -133,21 +137,4 @@ describe('Module mocking', () => {
133137
const newNow = require('react-native').now;
134138
expect(newNow).not.toBe(oldNow);
135139
});
136-
137-
it('should unmock all modules when clearMocks is called', () => {
138-
// Mock a module
139-
const mockFactory = () => ({ mockProperty: 'mocked' });
140-
mock('react-native', mockFactory);
141-
142-
// Verify it's mocked
143-
const module = require('react-native');
144-
expect(module.mockProperty).toBe('mocked');
145-
146-
// Unmock all modules
147-
clearMocks();
148-
149-
// Verify it's back to actual
150-
const actualModule = require('react-native');
151-
expect(actualModule).not.toHaveProperty('mockProperty');
152-
});
153140
});

packages/metro/src/moduleSystem.ts

Lines changed: 0 additions & 61 deletions
This file was deleted.

packages/metro/src/withRnHarness.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { MetroConfig } from 'metro-config';
22
import { getConfig } from '@react-native-harness/config';
3-
import { patchModuleSystem } from './moduleSystem';
43
import { getHarnessResolver } from './resolver';
54
import { getHarnessManifest } from './manifest';
65
import { getHarnessBabelTransformerPath } from './babel-transformer';
@@ -25,8 +24,6 @@ export const withRnHarness = <T extends MetroConfig>(
2524
const metroConfig = await config;
2625
const { config: harnessConfig } = await getConfig(process.cwd());
2726

28-
patchModuleSystem();
29-
3027
const harnessResolver = getHarnessResolver(metroConfig, harnessConfig);
3128
const harnessManifest = getHarnessManifest(harnessConfig);
3229
const harnessBabelTransformerPath =
@@ -40,6 +37,9 @@ export const withRnHarness = <T extends MetroConfig>(
4037
getPolyfills: (...args) => [
4138
...(metroConfig.serializer?.getPolyfills?.(...args) ?? []),
4239
harnessManifest,
40+
require.resolve(
41+
'@react-native-harness/runtime/polyfills/harness-module-system'
42+
),
4343
],
4444
isThirdPartyModule({ path: modulePath }) {
4545
const isThirdPartyByDefault =
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// @ts-nocheck
2+
/* eslint-disable */
3+
4+
// This file is a polyfill that monkey-patches the Metro module system
5+
// to allow capturing nested require calls.
6+
7+
(function (globalObject) {
8+
const myRequire = function (id) {
9+
return globalObject.__r(id);
10+
};
11+
12+
const myImportDefault = function (id) {
13+
return globalObject.__r.importDefault(id);
14+
};
15+
16+
const myImportAll = function (id) {
17+
return globalObject.__r.importAll(id);
18+
};
19+
20+
// Monkey-patch define
21+
const originalDefine = globalObject.__d;
22+
globalObject.__d = function (factory, moduleId, dependencyMap) {
23+
const wrappedFactory = function (...args) {
24+
// Standard Metro with import support (7 arguments)
25+
// args: global, require, importDefault, importAll, module, exports, dependencyMap
26+
const global = args[0];
27+
const moduleObject = args[4];
28+
const exports = args[5];
29+
const depMap = args[6];
30+
31+
return factory(
32+
global,
33+
myRequire,
34+
myImportDefault,
35+
myImportAll,
36+
moduleObject,
37+
exports,
38+
depMap
39+
);
40+
};
41+
42+
// Call the original define with the wrapped factory
43+
return originalDefine.call(this, wrappedFactory, moduleId, dependencyMap);
44+
};
45+
46+
globalObject.__resetModule = function (moduleId) {
47+
const module = globalObject.__r.getModules().get(moduleId);
48+
49+
if (!module) {
50+
return;
51+
}
52+
53+
module.hasError = false;
54+
module.error = undefined;
55+
module.isInitialized = false;
56+
};
57+
58+
globalObject.__resetModules = function () {
59+
const modules = globalObject.__r.getModules();
60+
61+
modules.forEach(function (mod, moduleId) {
62+
globalObject.__resetModule(moduleId);
63+
});
64+
};
65+
})(
66+
typeof globalThis !== 'undefined'
67+
? globalThis
68+
: typeof global !== 'undefined'
69+
? global
70+
: typeof window !== 'undefined'
71+
? window
72+
: this
73+
);

0 commit comments

Comments
 (0)