Skip to content

Commit bcac0d4

Browse files
committed
feat(runtime): add jest.mockModule
1 parent 90d6908 commit bcac0d4

File tree

3 files changed

+80
-9
lines changed

3 files changed

+80
-9
lines changed

e2e/native-esm/__tests__/native-esm.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ test('require of ESM should throw correct error', () => {
179179
});
180180

181181
test('can mock module', async () => {
182-
jestObject.unstable_mockModule('../mockedModule.mjs', () => ({foo: 'bar'}), {
182+
jestObject.mockModule('../mockedModule.mjs', () => ({foo: 'bar'}), {
183183
virtual: true,
184184
});
185185

packages/jest-environment/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,9 @@ export interface Jest {
140140
/**
141141
* Mocks a module with the provided module factory when it is being imported.
142142
*/
143-
unstable_mockModule<T = unknown>(
143+
mockModule<T = unknown>(
144144
moduleName: string,
145-
moduleFactory: () => Promise<T> | T,
145+
moduleFactory?: () => Promise<T> | T,
146146
options?: {virtual?: boolean},
147147
): Jest;
148148
/**

packages/jest-runtime/src/index.ts

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,8 @@ export default class Runtime {
581581
async unstable_importModule(
582582
from: Config.Path,
583583
moduleName?: string,
584+
// TODO: implement this
585+
_isImportActual = false,
584586
): Promise<void> {
585587
invariant(
586588
runtimeSupportsVmModules,
@@ -659,6 +661,7 @@ export default class Runtime {
659661
this.setExport(key, value);
660662
});
661663
},
664+
// should identifier be `node://${moduleName}`?
662665
{context, identifier: moduleName},
663666
);
664667

@@ -667,7 +670,69 @@ export default class Runtime {
667670
return evaluateSyntheticModule(module);
668671
}
669672

670-
throw new Error('Attempting to import a mock without a factory');
673+
const manualMockOrStub = this._resolver.getMockModule(from, moduleName);
674+
675+
let modulePath =
676+
this._resolver.getMockModule(from, moduleName) ||
677+
this._resolveModule(from, moduleName);
678+
679+
let isManualMock =
680+
manualMockOrStub &&
681+
!this._resolver.resolveStubModuleName(from, moduleName);
682+
if (!isManualMock) {
683+
// If the actual module file has a __mocks__ dir sitting immediately next
684+
// to it, look to see if there is a manual mock for this file.
685+
//
686+
// subDir1/my_module.js
687+
// subDir1/__mocks__/my_module.js
688+
// subDir2/my_module.js
689+
// subDir2/__mocks__/my_module.js
690+
//
691+
// Where some other module does a relative require into each of the
692+
// respective subDir{1,2} directories and expects a manual mock
693+
// corresponding to that particular my_module.js file.
694+
695+
const moduleDir = path.dirname(modulePath);
696+
const moduleFileName = path.basename(modulePath);
697+
const potentialManualMock = path.join(
698+
moduleDir,
699+
'__mocks__',
700+
moduleFileName,
701+
);
702+
if (fs.existsSync(potentialManualMock)) {
703+
isManualMock = true;
704+
modulePath = potentialManualMock;
705+
}
706+
}
707+
if (isManualMock) {
708+
const localModule: InitialModule = {
709+
children: [],
710+
exports: {},
711+
filename: modulePath,
712+
id: modulePath,
713+
loaded: false,
714+
path: modulePath,
715+
};
716+
717+
this._loadModule(
718+
localModule,
719+
from,
720+
moduleName,
721+
modulePath,
722+
undefined,
723+
this._moduleMockRegistry,
724+
);
725+
726+
this._moduleMockRegistry.set(moduleID, localModule.exports);
727+
} else {
728+
// Look for a real module to generate an automock from
729+
this._moduleMockRegistry.set(
730+
moduleID,
731+
this._generateMock(from, moduleName),
732+
);
733+
}
734+
735+
return this._moduleMockRegistry.get(moduleID);
671736
}
672737

673738
private getExportsOfCjs(modulePath: Config.Path) {
@@ -1767,16 +1832,22 @@ export default class Runtime {
17671832
this.setMock(from, moduleName, mockFactory, options);
17681833
return jestObject;
17691834
};
1770-
const mockModule: Jest['unstable_mockModule'] = (
1835+
const mockModule: Jest['mockModule'] = (
17711836
moduleName,
17721837
mockFactory,
17731838
options,
17741839
) => {
1775-
if (typeof mockFactory !== 'function') {
1776-
throw new Error('`unstable_mockModule` must be passed a mock factory');
1840+
if (mockFactory !== undefined) {
1841+
this.setModuleMock(from, moduleName, mockFactory, options);
1842+
return jestObject;
17771843
}
17781844

1779-
this.setModuleMock(from, moduleName, mockFactory, options);
1845+
const moduleID = this._resolver.getModuleID(
1846+
this._virtualMocks,
1847+
from,
1848+
moduleName,
1849+
);
1850+
this._explicitShouldMockModule.set(moduleID, true);
17801851
return jestObject;
17811852
};
17821853
const clearAllMocks = () => {
@@ -1877,6 +1948,7 @@ export default class Runtime {
18771948
isMockFunction: this._moduleMocker.isMockFunction,
18781949
isolateModules,
18791950
mock,
1951+
mockModule,
18801952
requireActual: this.requireActual.bind(this, from),
18811953
requireMock: this.requireMock.bind(this, from),
18821954
resetAllMocks,
@@ -1913,7 +1985,6 @@ export default class Runtime {
19131985
setTimeout,
19141986
spyOn,
19151987
unmock,
1916-
unstable_mockModule: mockModule,
19171988
useFakeTimers,
19181989
useRealTimers,
19191990
};

0 commit comments

Comments
 (0)