@@ -180,6 +180,7 @@ export default class Runtime {
180180 private _currentlyExecutingModulePath : string ;
181181 private readonly _environment : JestEnvironment ;
182182 private readonly _explicitShouldMock : Map < string , boolean > ;
183+ private readonly _explicitShouldMockModule : Map < string , boolean > ;
183184 private _fakeTimersImplementation :
184185 | LegacyFakeTimers < unknown >
185186 | ModernFakeTimers
@@ -194,6 +195,8 @@ export default class Runtime {
194195 > ;
195196 private _mockRegistry : Map < string , any > ;
196197 private _isolatedMockRegistry : Map < string , any > | null ;
198+ private _moduleMockRegistry : Map < string , VMModule > ;
199+ private readonly _moduleMockFactories : Map < string , ( ) => unknown > ;
197200 private readonly _moduleMocker : ModuleMocker ;
198201 private _isolatedModuleRegistry : ModuleRegistry | null ;
199202 private _moduleRegistry : ModuleRegistry ;
@@ -217,6 +220,7 @@ export default class Runtime {
217220 private readonly _transitiveShouldMock : Map < string , boolean > ;
218221 private _unmockList : RegExp | undefined ;
219222 private readonly _virtualMocks : Map < string , boolean > ;
223+ private readonly _virtualModuleMocks : Map < string , boolean > ;
220224 private _moduleImplementation ?: typeof nativeModule . Module ;
221225 private readonly jestObjectCaches : Map < string , Jest > ;
222226 private jestGlobals ?: JestGlobals ;
@@ -236,11 +240,14 @@ export default class Runtime {
236240 this . _currentlyExecutingModulePath = '' ;
237241 this . _environment = environment ;
238242 this . _explicitShouldMock = new Map ( ) ;
243+ this . _explicitShouldMockModule = new Map ( ) ;
239244 this . _internalModuleRegistry = new Map ( ) ;
240245 this . _isCurrentlyExecutingManualMock = null ;
241246 this . _mainModule = null ;
242247 this . _mockFactories = new Map ( ) ;
243248 this . _mockRegistry = new Map ( ) ;
249+ this . _moduleMockRegistry = new Map ( ) ;
250+ this . _moduleMockFactories = new Map ( ) ;
244251 invariant (
245252 this . _environment . moduleMocker ,
246253 '`moduleMocker` must be set on an environment when created' ,
@@ -260,6 +267,7 @@ export default class Runtime {
260267 this . _fileTransforms = new Map ( ) ;
261268 this . _fileTransformsMutex = new Map ( ) ;
262269 this . _virtualMocks = new Map ( ) ;
270+ this . _virtualModuleMocks = new Map ( ) ;
263271 this . jestObjectCaches = new Map ( ) ;
264272
265273 this . _mockMetaDataCache = new Map ( ) ;
@@ -523,6 +531,16 @@ export default class Runtime {
523531
524532 const [ path , query ] = specifier . split ( '?' ) ;
525533
534+ if (
535+ this . _shouldMock (
536+ referencingIdentifier ,
537+ path ,
538+ this . _explicitShouldMockModule ,
539+ )
540+ ) {
541+ return this . importMock ( referencingIdentifier , path , context ) ;
542+ }
543+
526544 const resolved = this . _resolveModule ( referencingIdentifier , path ) ;
527545
528546 if (
@@ -612,6 +630,46 @@ export default class Runtime {
612630 return evaluateSyntheticModule ( module ) ;
613631 }
614632
633+ private async importMock < T = unknown > (
634+ from : Config . Path ,
635+ moduleName : string ,
636+ context : VMContext ,
637+ ) : Promise < T > {
638+ const moduleID = this . _resolver . getModuleID (
639+ this . _virtualModuleMocks ,
640+ from ,
641+ moduleName ,
642+ ) ;
643+
644+ if ( this . _moduleMockRegistry . has ( moduleID ) ) {
645+ return this . _moduleMockRegistry . get ( moduleID ) ;
646+ }
647+
648+ if ( this . _moduleMockFactories . has ( moduleID ) ) {
649+ const invokedFactory : any = await this . _moduleMockFactories . get (
650+ moduleID ,
651+ // has check above makes this ok
652+ ) ! ( ) ;
653+
654+ const module = new SyntheticModule (
655+ Object . keys ( invokedFactory ) ,
656+ function ( ) {
657+ Object . entries ( invokedFactory ) . forEach ( ( [ key , value ] ) => {
658+ // @ts -expect-error: TS doesn't know what `this` is
659+ this . setExport ( key , value ) ;
660+ } ) ;
661+ } ,
662+ { context, identifier : moduleName } ,
663+ ) ;
664+
665+ this . _moduleMockRegistry . set ( moduleID , module ) ;
666+
667+ return evaluateSyntheticModule ( module ) ;
668+ }
669+
670+ throw new Error ( 'Attempting to import a mock without a factory' ) ;
671+ }
672+
615673 private getExportsOfCjs ( modulePath : Config . Path ) {
616674 const cachedNamedExports = this . _cjsNamedExports . get ( modulePath ) ;
617675
@@ -643,7 +701,7 @@ export default class Runtime {
643701 from : Config . Path ,
644702 moduleName ?: string ,
645703 options ?: InternalModuleOptions ,
646- isRequireActual ?: boolean | null ,
704+ isRequireActual = false ,
647705 ) : T {
648706 const moduleID = this . _resolver . getModuleID (
649707 this . _virtualMocks ,
@@ -770,17 +828,12 @@ export default class Runtime {
770828 moduleName ,
771829 ) ;
772830
773- if (
774- this . _isolatedMockRegistry &&
775- this . _isolatedMockRegistry . get ( moduleID )
776- ) {
777- return this . _isolatedMockRegistry . get ( moduleID ) ;
778- } else if ( this . _mockRegistry . get ( moduleID ) ) {
779- return this . _mockRegistry . get ( moduleID ) ;
780- }
781-
782831 const mockRegistry = this . _isolatedMockRegistry || this . _mockRegistry ;
783832
833+ if ( mockRegistry . get ( moduleID ) ) {
834+ return mockRegistry . get ( moduleID ) ;
835+ }
836+
784837 if ( this . _mockFactories . has ( moduleID ) ) {
785838 // has check above makes this ok
786839 const module = this . _mockFactories . get ( moduleID ) ! ( ) ;
@@ -896,7 +949,7 @@ export default class Runtime {
896949 }
897950
898951 try {
899- if ( this . _shouldMock ( from , moduleName ) ) {
952+ if ( this . _shouldMock ( from , moduleName , this . _explicitShouldMock ) ) {
900953 return this . requireMock < T > ( from , moduleName ) ;
901954 } else {
902955 return this . requireModule < T > ( from , moduleName ) ;
@@ -952,6 +1005,7 @@ export default class Runtime {
9521005 this . _moduleRegistry . clear ( ) ;
9531006 this . _esmoduleRegistry . clear ( ) ;
9541007 this . _cjsNamedExports . clear ( ) ;
1008+ this . _moduleMockRegistry . clear ( ) ;
9551009
9561010 if ( this . _environment ) {
9571011 if ( this . _environment . global ) {
@@ -1043,6 +1097,26 @@ export default class Runtime {
10431097 this . _mockFactories . set ( moduleID , mockFactory ) ;
10441098 }
10451099
1100+ private setModuleMock (
1101+ from : string ,
1102+ moduleName : string ,
1103+ mockFactory : ( ) => Promise < unknown > | unknown ,
1104+ options ?: { virtual ?: boolean } ,
1105+ ) : void {
1106+ if ( options ?. virtual ) {
1107+ const mockPath = this . _resolver . getModulePath ( from , moduleName ) ;
1108+
1109+ this . _virtualModuleMocks . set ( mockPath , true ) ;
1110+ }
1111+ const moduleID = this . _resolver . getModuleID (
1112+ this . _virtualModuleMocks ,
1113+ from ,
1114+ moduleName ,
1115+ ) ;
1116+ this . _explicitShouldMockModule . set ( moduleID , true ) ;
1117+ this . _moduleMockFactories . set ( moduleID , mockFactory ) ;
1118+ }
1119+
10461120 restoreAllMocks ( ) : void {
10471121 this . _moduleMocker . restoreAllMocks ( ) ;
10481122 }
@@ -1063,12 +1137,15 @@ export default class Runtime {
10631137 this . _internalModuleRegistry . clear ( ) ;
10641138 this . _mainModule = null ;
10651139 this . _mockFactories . clear ( ) ;
1140+ this . _moduleMockFactories . clear ( ) ;
10661141 this . _mockMetaDataCache . clear ( ) ;
10671142 this . _shouldMockModuleCache . clear ( ) ;
10681143 this . _shouldUnmockTransitiveDependenciesCache . clear ( ) ;
10691144 this . _explicitShouldMock . clear ( ) ;
1145+ this . _explicitShouldMockModule . clear ( ) ;
10701146 this . _transitiveShouldMock . clear ( ) ;
10711147 this . _virtualMocks . clear ( ) ;
1148+ this . _virtualModuleMocks . clear ( ) ;
10721149 this . _cacheFS . clear ( ) ;
10731150 this . _unmockList = undefined ;
10741151
@@ -1516,8 +1593,11 @@ export default class Runtime {
15161593 ) ;
15171594 }
15181595
1519- private _shouldMock ( from : Config . Path , moduleName : string ) : boolean {
1520- const explicitShouldMock = this . _explicitShouldMock ;
1596+ private _shouldMock (
1597+ from : Config . Path ,
1598+ moduleName : string ,
1599+ explicitShouldMock : Map < string , boolean > ,
1600+ ) : boolean {
15211601 const moduleID = this . _resolver . getModuleID (
15221602 this . _virtualMocks ,
15231603 from ,
@@ -1687,6 +1767,18 @@ export default class Runtime {
16871767 this . setMock ( from , moduleName , mockFactory , options ) ;
16881768 return jestObject ;
16891769 } ;
1770+ const mockModule : Jest [ 'unstable_mockModule' ] = (
1771+ moduleName ,
1772+ mockFactory ,
1773+ options ,
1774+ ) => {
1775+ if ( typeof mockFactory !== 'function' ) {
1776+ throw new Error ( '`unstable_mockModule` must be passed a mock factory' ) ;
1777+ }
1778+
1779+ this . setModuleMock ( from , moduleName , mockFactory , options ) ;
1780+ return jestObject ;
1781+ } ;
16901782 const clearAllMocks = ( ) => {
16911783 this . clearAllMocks ( ) ;
16921784 return jestObject ;
@@ -1821,6 +1913,7 @@ export default class Runtime {
18211913 setTimeout,
18221914 spyOn,
18231915 unmock,
1916+ unstable_mockModule : mockModule ,
18241917 useFakeTimers,
18251918 useRealTimers,
18261919 } ;
0 commit comments