@@ -180,6 +180,7 @@ export default class Runtime {
180
180
private _currentlyExecutingModulePath : string ;
181
181
private readonly _environment : JestEnvironment ;
182
182
private readonly _explicitShouldMock : Map < string , boolean > ;
183
+ private readonly _explicitShouldMockModule : Map < string , boolean > ;
183
184
private _fakeTimersImplementation :
184
185
| LegacyFakeTimers < unknown >
185
186
| ModernFakeTimers
@@ -194,6 +195,8 @@ export default class Runtime {
194
195
> ;
195
196
private _mockRegistry : Map < string , any > ;
196
197
private _isolatedMockRegistry : Map < string , any > | null ;
198
+ private _moduleMockRegistry : Map < string , VMModule > ;
199
+ private readonly _moduleMockFactories : Map < string , ( ) => unknown > ;
197
200
private readonly _moduleMocker : ModuleMocker ;
198
201
private _isolatedModuleRegistry : ModuleRegistry | null ;
199
202
private _moduleRegistry : ModuleRegistry ;
@@ -217,6 +220,7 @@ export default class Runtime {
217
220
private readonly _transitiveShouldMock : Map < string , boolean > ;
218
221
private _unmockList : RegExp | undefined ;
219
222
private readonly _virtualMocks : Map < string , boolean > ;
223
+ private readonly _virtualModuleMocks : Map < string , boolean > ;
220
224
private _moduleImplementation ?: typeof nativeModule . Module ;
221
225
private readonly jestObjectCaches : Map < string , Jest > ;
222
226
private jestGlobals ?: JestGlobals ;
@@ -236,11 +240,14 @@ export default class Runtime {
236
240
this . _currentlyExecutingModulePath = '' ;
237
241
this . _environment = environment ;
238
242
this . _explicitShouldMock = new Map ( ) ;
243
+ this . _explicitShouldMockModule = new Map ( ) ;
239
244
this . _internalModuleRegistry = new Map ( ) ;
240
245
this . _isCurrentlyExecutingManualMock = null ;
241
246
this . _mainModule = null ;
242
247
this . _mockFactories = new Map ( ) ;
243
248
this . _mockRegistry = new Map ( ) ;
249
+ this . _moduleMockRegistry = new Map ( ) ;
250
+ this . _moduleMockFactories = new Map ( ) ;
244
251
invariant (
245
252
this . _environment . moduleMocker ,
246
253
'`moduleMocker` must be set on an environment when created' ,
@@ -260,6 +267,7 @@ export default class Runtime {
260
267
this . _fileTransforms = new Map ( ) ;
261
268
this . _fileTransformsMutex = new Map ( ) ;
262
269
this . _virtualMocks = new Map ( ) ;
270
+ this . _virtualModuleMocks = new Map ( ) ;
263
271
this . jestObjectCaches = new Map ( ) ;
264
272
265
273
this . _mockMetaDataCache = new Map ( ) ;
@@ -523,6 +531,16 @@ export default class Runtime {
523
531
524
532
const [ path , query ] = specifier . split ( '?' ) ;
525
533
534
+ if (
535
+ this . _shouldMock (
536
+ referencingIdentifier ,
537
+ path ,
538
+ this . _explicitShouldMockModule ,
539
+ )
540
+ ) {
541
+ return this . importMock ( referencingIdentifier , path , context ) ;
542
+ }
543
+
526
544
const resolved = this . _resolveModule ( referencingIdentifier , path ) ;
527
545
528
546
if (
@@ -612,6 +630,46 @@ export default class Runtime {
612
630
return evaluateSyntheticModule ( module ) ;
613
631
}
614
632
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
+
615
673
private getExportsOfCjs ( modulePath : Config . Path ) {
616
674
const cachedNamedExports = this . _cjsNamedExports . get ( modulePath ) ;
617
675
@@ -643,7 +701,7 @@ export default class Runtime {
643
701
from : Config . Path ,
644
702
moduleName ?: string ,
645
703
options ?: InternalModuleOptions ,
646
- isRequireActual ?: boolean | null ,
704
+ isRequireActual = false ,
647
705
) : T {
648
706
const moduleID = this . _resolver . getModuleID (
649
707
this . _virtualMocks ,
@@ -770,17 +828,12 @@ export default class Runtime {
770
828
moduleName ,
771
829
) ;
772
830
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
-
782
831
const mockRegistry = this . _isolatedMockRegistry || this . _mockRegistry ;
783
832
833
+ if ( mockRegistry . get ( moduleID ) ) {
834
+ return mockRegistry . get ( moduleID ) ;
835
+ }
836
+
784
837
if ( this . _mockFactories . has ( moduleID ) ) {
785
838
// has check above makes this ok
786
839
const module = this . _mockFactories . get ( moduleID ) ! ( ) ;
@@ -896,7 +949,7 @@ export default class Runtime {
896
949
}
897
950
898
951
try {
899
- if ( this . _shouldMock ( from , moduleName ) ) {
952
+ if ( this . _shouldMock ( from , moduleName , this . _explicitShouldMock ) ) {
900
953
return this . requireMock < T > ( from , moduleName ) ;
901
954
} else {
902
955
return this . requireModule < T > ( from , moduleName ) ;
@@ -952,6 +1005,7 @@ export default class Runtime {
952
1005
this . _moduleRegistry . clear ( ) ;
953
1006
this . _esmoduleRegistry . clear ( ) ;
954
1007
this . _cjsNamedExports . clear ( ) ;
1008
+ this . _moduleMockRegistry . clear ( ) ;
955
1009
956
1010
if ( this . _environment ) {
957
1011
if ( this . _environment . global ) {
@@ -1043,6 +1097,26 @@ export default class Runtime {
1043
1097
this . _mockFactories . set ( moduleID , mockFactory ) ;
1044
1098
}
1045
1099
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
+
1046
1120
restoreAllMocks ( ) : void {
1047
1121
this . _moduleMocker . restoreAllMocks ( ) ;
1048
1122
}
@@ -1063,12 +1137,15 @@ export default class Runtime {
1063
1137
this . _internalModuleRegistry . clear ( ) ;
1064
1138
this . _mainModule = null ;
1065
1139
this . _mockFactories . clear ( ) ;
1140
+ this . _moduleMockFactories . clear ( ) ;
1066
1141
this . _mockMetaDataCache . clear ( ) ;
1067
1142
this . _shouldMockModuleCache . clear ( ) ;
1068
1143
this . _shouldUnmockTransitiveDependenciesCache . clear ( ) ;
1069
1144
this . _explicitShouldMock . clear ( ) ;
1145
+ this . _explicitShouldMockModule . clear ( ) ;
1070
1146
this . _transitiveShouldMock . clear ( ) ;
1071
1147
this . _virtualMocks . clear ( ) ;
1148
+ this . _virtualModuleMocks . clear ( ) ;
1072
1149
this . _cacheFS . clear ( ) ;
1073
1150
this . _unmockList = undefined ;
1074
1151
@@ -1516,8 +1593,11 @@ export default class Runtime {
1516
1593
) ;
1517
1594
}
1518
1595
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 {
1521
1601
const moduleID = this . _resolver . getModuleID (
1522
1602
this . _virtualMocks ,
1523
1603
from ,
@@ -1687,6 +1767,18 @@ export default class Runtime {
1687
1767
this . setMock ( from , moduleName , mockFactory , options ) ;
1688
1768
return jestObject ;
1689
1769
} ;
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
+ } ;
1690
1782
const clearAllMocks = ( ) => {
1691
1783
this . clearAllMocks ( ) ;
1692
1784
return jestObject ;
@@ -1821,6 +1913,7 @@ export default class Runtime {
1821
1913
setTimeout,
1822
1914
spyOn,
1823
1915
unmock,
1916
+ unstable_mockModule : mockModule ,
1824
1917
useFakeTimers,
1825
1918
useRealTimers,
1826
1919
} ;
0 commit comments