Skip to content

Commit a96b0e4

Browse files
robhoganmeta-codesync[bot]
authored andcommitted
Don't rely on inline requires to apply mocks in JS tests (#59307)
Summary: Test only change. RN's own Jest transform (`jest/preprocessor.js`, for this repo's own tests, not public) doesn't include `babel-plugin-jest-hoist`, so `jest.mock` calls run where they're written. Several tests only saw their mocks because inline requires deferred the `require` of the module under test (or of its dependencies) until first use. That only works for destructured requires like `const {foo} = require('./foo')` while the Babel preset lowers destructuring, which in this config it currently does only as a side effect of lowering generators with regenerator. #59303 removes that, and 114 tests across these 16 suites fail. This makes the tests install their mocks before the modules under test load, so they pass either way, without diverging Jest's transform from the app's: - Require modules under test after the `jest.mock` calls that affect them. - Use mock factories instead of spies or automocks where the code under test binds a function when it loads, or loads an ESM-only package (`octokit/rest`). - Re-require modules after `jest.resetModules()` where tests reset the registry (`publish-npm`, `Animated`). The release script tests in particular are dangerous when their mocks silently stop applying - locally, they ran the real version bump against the checkout. ## Changelog: [INTERNAL] [CHANGED] - Don't rely on inline requires to apply mocks in JS tests Pull Request resolved: #59307 Test Plan: The full Jest suite passes on this PR (6,284 tests), and with #59303 applied on top (6,285), both under Node 24 with `--no-cache`. Without this PR, #59303 fails 114 tests in 16 suites, matching its CI. Reviewed By: christophpurrer Differential Revision: D126757286 Pulled By: javache fbshipit-source-id: ce39217579aa9805f74786ee9b669bf411e97191
1 parent 5b6f18b commit a96b0e4

16 files changed

Lines changed: 120 additions & 83 deletions

File tree

‎.github/workflow-scripts/__tests__/publishTemplate-test.js‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,6 @@
77
* @format
88
*/
99

10-
const {
11-
publishTemplate,
12-
verifyPublishedTemplate,
13-
} = require('../publishTemplate');
14-
1510
const mockRun = jest.fn();
1611
const mockSleep = jest.fn();
1712
const mockGetNpmPackageInfo = jest.fn();
@@ -29,6 +24,11 @@ jest.mock('../verifyPublishedPackage.js', () => ({
2924
verifyPublishedPackage: mockVerifyPublishedPackage,
3025
}));
3126

27+
const {
28+
publishTemplate,
29+
verifyPublishedTemplate,
30+
} = require('../publishTemplate');
31+
3232
const getMockGithub = () => ({
3333
rest: {
3434
actions: {

‎.github/workflow-scripts/__tests__/verifyArtifactsAreOnMaven-test.js‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
* @format
88
*/
99

10-
const {verifyArtifactsAreOnMaven} = require('../verifyArtifactsAreOnMaven');
11-
1210
const mockSleep = jest.fn();
1311
const silence = () => {};
1412
const mockFetch = jest.fn();
@@ -19,6 +17,8 @@ jest.mock('../utils.js', () => ({
1917
sleep: mockSleep,
2018
}));
2119

20+
const {verifyArtifactsAreOnMaven} = require('../verifyArtifactsAreOnMaven');
21+
2222
process.exit = mockExit;
2323
global.fetch = mockFetch;
2424

‎.github/workflow-scripts/__tests__/verifyPublishedPackage-test.js‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
* @format
88
*/
99

10-
const {verifyPublishedPackage} = require('../verifyPublishedPackage');
11-
1210
const mockRun = jest.fn();
1311
const mockSleep = jest.fn();
1412
const mockGetNpmPackageInfo = jest.fn();
@@ -23,6 +21,8 @@ jest.mock('../utils.js', () => ({
2321
getNpmPackageInfo: mockGetNpmPackageInfo,
2422
}));
2523

24+
const {verifyPublishedPackage} = require('../verifyPublishedPackage');
25+
2626
describe('#verifyPublishedPackage', () => {
2727
beforeEach(jest.clearAllMocks);
2828

‎.github/workflow-scripts/__tests__/verifyReleaseOnNpm-test.js‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
* @format
88
*/
99

10-
const {verifyReleaseOnNpm} = require('../verifyReleaseOnNpm');
11-
1210
const mockVerifyPublishedPackage = jest.fn();
1311
const silence = () => {};
1412

@@ -21,6 +19,8 @@ jest.mock('../verifyPublishedPackage.js', () => ({
2119
verifyPublishedPackage: mockVerifyPublishedPackage,
2220
}));
2321

22+
const {verifyReleaseOnNpm} = require('../verifyReleaseOnNpm');
23+
2424
describe('#verifyReleaseOnNPM', () => {
2525
beforeEach(jest.clearAllMocks);
2626

‎packages/react-native-codegen/src/parsers/__tests__/parsers-commons-test.js‎

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,33 @@
1010

1111
'use-strict';
1212

13+
// The buildSchema tests spy on `getConfigType`, which parsers-commons reads when
14+
// it loads, so it's wrapped before any module that uses it is required.
15+
jest.mock('../utils', () => {
16+
const actual = jest.requireActual<Utils>('../utils');
17+
return {...actual, getConfigType: jest.fn(actual.getConfigType)};
18+
});
19+
1320
import type {ParserType} from '../errors';
21+
import typeof * as Utils from '../utils';
1422

15-
import {FlowParser} from '../flow/parser';
16-
import {MockedParser} from '../parserMock';
17-
import {
23+
const {
24+
IncorrectModuleRegistryCallArgumentTypeParserError,
25+
IncorrectModuleRegistryCallArityParserError,
26+
MisnamedModuleInterfaceParserError,
27+
ModuleInterfaceNotFoundParserError,
28+
MoreThanOneModuleInterfaceParserError,
29+
MoreThanOneModuleRegistryCallsParserError,
30+
ParserError,
31+
UnsupportedObjectPropertyTypeAnnotationParserError,
32+
UntypedModuleRegistryCallParserError,
33+
UnusedModuleInterfaceParserError,
34+
} = require('../errors');
35+
const {buildComponentSchema} = require('../flow/components');
36+
const {flowTranslateTypeAnnotation} = require('../flow/modules/index');
37+
const {FlowParser} = require('../flow/parser');
38+
const {MockedParser} = require('../parserMock');
39+
const {
1840
assertGenericTypeAnnotationHasExactlyOneTypeParameter,
1941
buildSchema,
2042
buildSchemaFromConfigType,
@@ -30,22 +52,7 @@ import {
3052
propertyNames,
3153
unwrapNullable,
3254
wrapNullable,
33-
} from '../parsers-commons';
34-
35-
const {
36-
IncorrectModuleRegistryCallArgumentTypeParserError,
37-
IncorrectModuleRegistryCallArityParserError,
38-
MisnamedModuleInterfaceParserError,
39-
ModuleInterfaceNotFoundParserError,
40-
MoreThanOneModuleInterfaceParserError,
41-
MoreThanOneModuleRegistryCallsParserError,
42-
ParserError,
43-
UnsupportedObjectPropertyTypeAnnotationParserError,
44-
UntypedModuleRegistryCallParserError,
45-
UnusedModuleInterfaceParserError,
46-
} = require('../errors');
47-
const {buildComponentSchema} = require('../flow/components');
48-
const {flowTranslateTypeAnnotation} = require('../flow/modules/index');
55+
} = require('../parsers-commons');
4956
const {buildModuleSchema} = require('../parsers-commons.js');
5057
const {Visitor} = require('../parsers-primitives');
5158
const {wrapComponentSchema} = require('../schema.js');
@@ -665,7 +672,7 @@ describe('buildSchemaFromConfigType', () => {
665672
});
666673

667674
describe('buildSchema', () => {
668-
const getConfigTypeSpy = jest.spyOn(require('../utils'), 'getConfigType');
675+
const getConfigTypeSpy = require('../utils').getConfigType;
669676

670677
describe('when there is no codegenNativeComponent and no TurboModule', () => {
671678
const contents = '';

‎packages/react-native/Libraries/Animated/__tests__/Animated-test.js‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ import * as React from 'react';
1212

1313
const {PlatformColor} = require('../../StyleSheet/PlatformColorValueTypes');
1414
let Animated = require('../Animated').default;
15-
const AnimatedProps = require('../nodes/AnimatedProps').default;
16-
const {
15+
let AnimatedProps = require('../nodes/AnimatedProps').default;
16+
let {
1717
create,
1818
unmount,
1919
update,
2020
} = require('@react-native/jest-preset/jest/renderer');
21-
const TestRenderer = require('react-test-renderer');
21+
let TestRenderer = require('react-test-renderer');
2222

2323
// WORKAROUND: `jest.runAllTicks` skips tasks scheduled w/ `queueMicrotask`.
2424
function mockQueueMicrotask() {
@@ -37,6 +37,16 @@ function mockQueueMicrotask() {
3737
describe('Animated', () => {
3838
beforeEach(() => {
3939
jest.resetModules();
40+
// Reload after the reset, so that Animated and the renderer share a copy
41+
// of React.
42+
Animated = require('../Animated').default;
43+
AnimatedProps = require('../nodes/AnimatedProps').default;
44+
({
45+
create,
46+
unmount,
47+
update,
48+
} = require('@react-native/jest-preset/jest/renderer'));
49+
TestRenderer = require('react-test-renderer');
4050
});
4151

4252
mockQueueMicrotask();

‎packages/react-native/Libraries/LogBox/__tests__/LogBoxInspectorContainer-test.js‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,6 @@
1010

1111
'use strict';
1212

13-
const BackHandler: $FlowFixMe = require('../../Utilities/BackHandler').default;
14-
const LogBoxData = require('../Data/LogBoxData');
15-
const LogBoxLog = require('../Data/LogBoxLog').default;
16-
const {
17-
_LogBoxInspectorContainer: LogBoxInspectorContainer,
18-
} = require('../LogBoxInspectorContainer');
19-
const render = require('@react-native/jest-preset/jest/renderer');
20-
const React = require('react');
21-
2213
// Mock `LogBoxInspector` because we are interested in snapshotting the behavior
2314
// of `LogBoxInspectorContainer`, not `LogBoxInspector`.
2415
jest.mock('../UI/LogBoxInspector', () => ({
@@ -30,6 +21,15 @@ jest.mock('../../Utilities/BackHandler', () =>
3021
require('../../Utilities/__mocks__/BackHandler'),
3122
);
3223

24+
const BackHandler: $FlowFixMe = require('../../Utilities/BackHandler').default;
25+
const LogBoxData = require('../Data/LogBoxData');
26+
const LogBoxLog = require('../Data/LogBoxLog').default;
27+
const {
28+
_LogBoxInspectorContainer: LogBoxInspectorContainer,
29+
} = require('../LogBoxInspectorContainer');
30+
const render = require('@react-native/jest-preset/jest/renderer');
31+
const React = require('react');
32+
3333
describe('LogBoxInspectorContainer', () => {
3434
let output;
3535

‎packages/react-native/Libraries/LogBox/__tests__/LogBoxNotificationContainer-test.js‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,6 @@
1010

1111
'use strict';
1212

13-
const BackHandler: $FlowFixMe = require('../../Utilities/BackHandler').default;
14-
const LogBoxData = require('../Data/LogBoxData');
15-
const LogBoxLog = require('../Data/LogBoxLog').default;
16-
const {LogBoxNotificationContainer} = require('../LogBoxNotificationContainer');
17-
const render = require('@react-native/jest-preset/jest/renderer');
18-
const React = require('react');
19-
2013
// Mock `LogBoxLogNotification` because we are interested in snapshotting the
2114
// behavior of `LogBoxNotificationContainer`, not `LogBoxLogNotification`.
2215
jest.mock('../UI/LogBoxNotification', () => ({
@@ -28,6 +21,13 @@ jest.mock('../../Utilities/BackHandler', () =>
2821
require('../../Utilities/__mocks__/BackHandler'),
2922
);
3023

24+
const BackHandler: $FlowFixMe = require('../../Utilities/BackHandler').default;
25+
const LogBoxData = require('../Data/LogBoxData');
26+
const LogBoxLog = require('../Data/LogBoxLog').default;
27+
const {LogBoxNotificationContainer} = require('../LogBoxNotificationContainer');
28+
const render = require('@react-native/jest-preset/jest/renderer');
29+
const React = require('react');
30+
3131
describe('LogBoxNotificationContainer', () => {
3232
let output;
3333

‎packages/react-native/scripts/ios-prebuild/__tests__/headers-xcframework-test.js‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,18 @@
1010

1111
'use strict';
1212

13+
// headers-xcframework reads `execFileSync` when it loads, so it's mocked before
14+
// the module is required.
15+
const mockExecFileSync = jest.fn();
16+
jest.mock('node:child_process', () => ({
17+
...jest.requireActual('node:child_process'),
18+
execFileSync: mockExecFileSync,
19+
}));
20+
1321
const {
1422
buildDepsHeadersXcframework,
1523
stubSlicesFromXcframework,
1624
} = require('../headers-xcframework');
17-
const childProcess = require('node:child_process');
1825
const fs = require('node:fs');
1926
const os = require('node:os');
2027
const path = require('node:path');
@@ -67,9 +74,9 @@ describe('stubSlicesFromXcframework', () => {
6774
// SupportedPlatform/Variant -> key mapping and the unknown-slice guard can be
6875
// tested without a real xcframework or macOS tooling.
6976
const mockPlist = (obj /*: unknown */) =>
70-
jest
71-
.spyOn(childProcess, 'execFileSync')
72-
.mockReturnValue(Buffer.from(JSON.stringify(obj) ?? '', 'utf8'));
77+
mockExecFileSync.mockReturnValue(
78+
Buffer.from(JSON.stringify(obj) ?? '', 'utf8'),
79+
);
7380

7481
afterEach(() => {
7582
jest.restoreAllMocks();

‎packages/react-native/scripts/swiftpm/__tests__/headers-utils-test.js‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@
1010

1111
'use strict';
1212

13+
// Mock all required modules
14+
jest.mock('../utils');
15+
jest.mock('../headers-mappings');
16+
jest.mock('node:fs');
17+
jest.mock('node:path');
18+
1319
const {
1420
symlinkCodegenHeaders,
1521
symlinkHeadersFromPath,
@@ -18,12 +24,6 @@ const {
1824
symlinkThirdPartyDependenciesHeaders,
1925
} = require('../headers-utils');
2026

21-
// Mock all required modules
22-
jest.mock('../utils');
23-
jest.mock('../headers-mappings');
24-
jest.mock('node:fs');
25-
jest.mock('node:path');
26-
2727
describe('symlinkHeadersFromPath', () => {
2828
let mockUtils;
2929
let mockFs;

0 commit comments

Comments
 (0)