0
# Module Overriding
1
2
Capability to replace entire modules with alternative implementations for testing scenarios with complex module dependencies. This is particularly useful for testing applications that depend on external modules or when you need to replace entire feature modules with simplified test versions.
3
4
## Capabilities
5
6
### Module Override Method
7
8
The TestingModuleBuilder provides a method to override entire modules with alternative implementations.
9
10
```typescript { .api }
11
class TestingModuleBuilder {
12
/**
13
* Override an entire module with a replacement module
14
* @param moduleToOverride - The module definition to replace
15
* @returns OverrideModule interface for specifying the replacement module
16
*/
17
overrideModule(moduleToOverride: ModuleDefinition): OverrideModule;
18
}
19
```
20
21
### OverrideModule Interface
22
23
Interface for specifying module replacement strategies.
24
25
```typescript { .api }
26
/**
27
* Interface for replacing modules in testing scenarios
28
*/
29
interface OverrideModule {
30
/**
31
* Replace the target module with a new module implementation
32
* @param newModule - The module definition to use as replacement
33
* @returns TestingModuleBuilder for method chaining
34
*/
35
useModule(newModule: ModuleDefinition): TestingModuleBuilder;
36
}
37
```
38
39
**Usage Examples:**
40
41
```typescript
42
import { Test, TestingModule } from "@nestjs/testing";
43
import { Module } from "@nestjs/common";
44
import { DatabaseModule } from "./database.module";
45
import { UsersModule } from "./users.module";
46
import { UsersService } from "./users.service";
47
48
// Create a test replacement for DatabaseModule
49
@Module({
50
providers: [
51
{
52
provide: "DATABASE_CONNECTION",
53
useValue: {
54
query: jest.fn(),
55
close: jest.fn(),
56
},
57
},
58
],
59
exports: ["DATABASE_CONNECTION"],
60
})
61
class TestDatabaseModule {}
62
63
describe("Module Overriding", () => {
64
let module: TestingModule;
65
66
beforeEach(async () => {
67
module = await Test.createTestingModule({
68
imports: [UsersModule], // UsersModule depends on DatabaseModule
69
})
70
.overrideModule(DatabaseModule)
71
.useModule(TestDatabaseModule)
72
.compile();
73
});
74
75
it("should use test database module", async () => {
76
const connection = module.get("DATABASE_CONNECTION");
77
expect(connection.query).toBeDefined();
78
expect(typeof connection.query).toBe("function");
79
});
80
});
81
```
82
83
### Advanced Module Override Patterns
84
85
**Dynamic Module Overriding:**
86
87
```typescript
88
import { DynamicModule } from "@nestjs/common";
89
90
// Original dynamic module
91
@Module({})
92
class ConfigModule {
93
static forRoot(options: ConfigOptions): DynamicModule {
94
return {
95
module: ConfigModule,
96
providers: [
97
{
98
provide: "CONFIG_OPTIONS",
99
useValue: options,
100
},
101
ConfigService,
102
],
103
exports: [ConfigService],
104
};
105
}
106
}
107
108
// Test replacement
109
@Module({})
110
class TestConfigModule {
111
static forRoot(): DynamicModule {
112
return {
113
module: TestConfigModule,
114
providers: [
115
{
116
provide: "CONFIG_OPTIONS",
117
useValue: { env: "test" },
118
},
119
{
120
provide: ConfigService,
121
useValue: {
122
get: jest.fn().mockReturnValue("test-value"),
123
},
124
},
125
],
126
exports: [ConfigService],
127
};
128
}
129
}
130
131
const module = await Test.createTestingModule({
132
imports: [AppModule], // AppModule imports ConfigModule.forRoot()
133
})
134
.overrideModule(ConfigModule)
135
.useModule(TestConfigModule.forRoot())
136
.compile();
137
```
138
139
**Third-party Module Overriding:**
140
141
```typescript
142
// Override external modules like TypeORM, Mongoose, etc.
143
import { TypeOrmModule } from "@nestjs/typeorm";
144
import { getRepositoryToken } from "@nestjs/typeorm";
145
import { User } from "./user.entity";
146
147
@Module({
148
providers: [
149
{
150
provide: getRepositoryToken(User),
151
useValue: {
152
find: jest.fn(),
153
findOne: jest.fn(),
154
save: jest.fn(),
155
delete: jest.fn(),
156
},
157
},
158
],
159
exports: [getRepositoryToken(User)],
160
})
161
class TestTypeOrmModule {}
162
163
const module = await Test.createTestingModule({
164
imports: [UsersModule], // UsersModule imports TypeOrmModule.forFeature([User])
165
})
166
.overrideModule(TypeOrmModule)
167
.useModule(TestTypeOrmModule)
168
.compile();
169
```
170
171
**Feature Module Simplification:**
172
173
```typescript
174
// Original complex feature module
175
@Module({
176
imports: [
177
DatabaseModule,
178
RedisModule,
179
EventEmitterModule,
180
HttpModule,
181
],
182
providers: [
183
ComplexService,
184
BackgroundProcessor,
185
ExternalApiClient,
186
],
187
exports: [ComplexService],
188
})
189
class FeatureModule {}
190
191
// Simplified test version
192
@Module({
193
providers: [
194
{
195
provide: ComplexService,
196
useValue: {
197
processData: jest.fn().mockResolvedValue("processed"),
198
validateInput: jest.fn().mockReturnValue(true),
199
},
200
},
201
],
202
exports: [ComplexService],
203
})
204
class TestFeatureModule {}
205
206
const module = await Test.createTestingModule({
207
imports: [AppModule],
208
})
209
.overrideModule(FeatureModule)
210
.useModule(TestFeatureModule)
211
.compile();
212
```
213
214
### Module Override with Providers
215
216
```typescript
217
// Override module and add additional test providers
218
@Module({
219
providers: [
220
// Mock the original module's providers
221
{
222
provide: OriginalService,
223
useValue: mockOriginalService,
224
},
225
// Add test-specific utilities
226
TestUtilityService,
227
],
228
exports: [OriginalService, TestUtilityService],
229
})
230
class TestReplacementModule {}
231
232
const module = await Test.createTestingModule({
233
imports: [MainModule],
234
})
235
.overrideModule(OriginalModule)
236
.useModule(TestReplacementModule)
237
.compile();
238
239
// Access both original and test utilities
240
const originalService = module.get<OriginalService>(OriginalService);
241
const testUtility = module.get<TestUtilityService>(TestUtilityService);
242
```
243
244
### Nested Module Overriding
245
246
```typescript
247
// When modules have nested dependencies
248
const module = await Test.createTestingModule({
249
imports: [AppModule],
250
})
251
// Override multiple modules in the dependency chain
252
.overrideModule(DatabaseModule)
253
.useModule(TestDatabaseModule)
254
.overrideModule(CacheModule)
255
.useModule(TestCacheModule)
256
.overrideModule(EventModule)
257
.useModule(TestEventModule)
258
.compile();
259
```
260
261
## Error Handling
262
263
Common scenarios and error handling when overriding modules:
264
265
```typescript
266
// Handle circular dependencies
267
try {
268
const module = await Test.createTestingModule({
269
imports: [ModuleWithCircularDep],
270
})
271
.overrideModule(CircularModule)
272
.useModule(TestCircularModule)
273
.compile();
274
} catch (error) {
275
// Handle circular dependency errors
276
console.error("Circular dependency detected:", error.message);
277
}
278
279
// Handle missing module dependencies
280
@Module({
281
imports: [MissingDependency], // This might cause issues
282
providers: [TestService],
283
})
284
class ProblematicTestModule {}
285
286
// Better approach - provide all necessary dependencies
287
@Module({
288
providers: [
289
TestService,
290
{
291
provide: "MISSING_DEPENDENCY",
292
useValue: mockDependency,
293
},
294
],
295
})
296
class WellFormedTestModule {}
297
```
298
299
## Types
300
301
```typescript { .api }
302
import { ModuleDefinition } from "@nestjs/core/interfaces/module-definition.interface";
303
import { TestingModuleBuilder } from "./testing-module.builder";
304
305
interface OverrideModule {
306
useModule: (newModule: ModuleDefinition) => TestingModuleBuilder;
307
}
308
309
// ModuleDefinition is typically a class decorated with @Module()
310
type ModuleDefinition = Function;
311
```