Comprehensive testing utilities for NestJS applications with dependency injection testing capabilities
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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.
The TestingModuleBuilder provides a method to override entire modules with alternative implementations.
class TestingModuleBuilder {
/**
* Override an entire module with a replacement module
* @param moduleToOverride - The module definition to replace
* @returns OverrideModule interface for specifying the replacement module
*/
overrideModule(moduleToOverride: ModuleDefinition): OverrideModule;
}Interface for specifying module replacement strategies.
/**
* Interface for replacing modules in testing scenarios
*/
interface OverrideModule {
/**
* Replace the target module with a new module implementation
* @param newModule - The module definition to use as replacement
* @returns TestingModuleBuilder for method chaining
*/
useModule(newModule: ModuleDefinition): TestingModuleBuilder;
}Usage Examples:
import { Test, TestingModule } from "@nestjs/testing";
import { Module } from "@nestjs/common";
import { DatabaseModule } from "./database.module";
import { UsersModule } from "./users.module";
import { UsersService } from "./users.service";
// Create a test replacement for DatabaseModule
@Module({
providers: [
{
provide: "DATABASE_CONNECTION",
useValue: {
query: jest.fn(),
close: jest.fn(),
},
},
],
exports: ["DATABASE_CONNECTION"],
})
class TestDatabaseModule {}
describe("Module Overriding", () => {
let module: TestingModule;
beforeEach(async () => {
module = await Test.createTestingModule({
imports: [UsersModule], // UsersModule depends on DatabaseModule
})
.overrideModule(DatabaseModule)
.useModule(TestDatabaseModule)
.compile();
});
it("should use test database module", async () => {
const connection = module.get("DATABASE_CONNECTION");
expect(connection.query).toBeDefined();
expect(typeof connection.query).toBe("function");
});
});Dynamic Module Overriding:
import { DynamicModule } from "@nestjs/common";
// Original dynamic module
@Module({})
class ConfigModule {
static forRoot(options: ConfigOptions): DynamicModule {
return {
module: ConfigModule,
providers: [
{
provide: "CONFIG_OPTIONS",
useValue: options,
},
ConfigService,
],
exports: [ConfigService],
};
}
}
// Test replacement
@Module({})
class TestConfigModule {
static forRoot(): DynamicModule {
return {
module: TestConfigModule,
providers: [
{
provide: "CONFIG_OPTIONS",
useValue: { env: "test" },
},
{
provide: ConfigService,
useValue: {
get: jest.fn().mockReturnValue("test-value"),
},
},
],
exports: [ConfigService],
};
}
}
const module = await Test.createTestingModule({
imports: [AppModule], // AppModule imports ConfigModule.forRoot()
})
.overrideModule(ConfigModule)
.useModule(TestConfigModule.forRoot())
.compile();Third-party Module Overriding:
// Override external modules like TypeORM, Mongoose, etc.
import { TypeOrmModule } from "@nestjs/typeorm";
import { getRepositoryToken } from "@nestjs/typeorm";
import { User } from "./user.entity";
@Module({
providers: [
{
provide: getRepositoryToken(User),
useValue: {
find: jest.fn(),
findOne: jest.fn(),
save: jest.fn(),
delete: jest.fn(),
},
},
],
exports: [getRepositoryToken(User)],
})
class TestTypeOrmModule {}
const module = await Test.createTestingModule({
imports: [UsersModule], // UsersModule imports TypeOrmModule.forFeature([User])
})
.overrideModule(TypeOrmModule)
.useModule(TestTypeOrmModule)
.compile();Feature Module Simplification:
// Original complex feature module
@Module({
imports: [
DatabaseModule,
RedisModule,
EventEmitterModule,
HttpModule,
],
providers: [
ComplexService,
BackgroundProcessor,
ExternalApiClient,
],
exports: [ComplexService],
})
class FeatureModule {}
// Simplified test version
@Module({
providers: [
{
provide: ComplexService,
useValue: {
processData: jest.fn().mockResolvedValue("processed"),
validateInput: jest.fn().mockReturnValue(true),
},
},
],
exports: [ComplexService],
})
class TestFeatureModule {}
const module = await Test.createTestingModule({
imports: [AppModule],
})
.overrideModule(FeatureModule)
.useModule(TestFeatureModule)
.compile();// Override module and add additional test providers
@Module({
providers: [
// Mock the original module's providers
{
provide: OriginalService,
useValue: mockOriginalService,
},
// Add test-specific utilities
TestUtilityService,
],
exports: [OriginalService, TestUtilityService],
})
class TestReplacementModule {}
const module = await Test.createTestingModule({
imports: [MainModule],
})
.overrideModule(OriginalModule)
.useModule(TestReplacementModule)
.compile();
// Access both original and test utilities
const originalService = module.get<OriginalService>(OriginalService);
const testUtility = module.get<TestUtilityService>(TestUtilityService);// When modules have nested dependencies
const module = await Test.createTestingModule({
imports: [AppModule],
})
// Override multiple modules in the dependency chain
.overrideModule(DatabaseModule)
.useModule(TestDatabaseModule)
.overrideModule(CacheModule)
.useModule(TestCacheModule)
.overrideModule(EventModule)
.useModule(TestEventModule)
.compile();Common scenarios and error handling when overriding modules:
// Handle circular dependencies
try {
const module = await Test.createTestingModule({
imports: [ModuleWithCircularDep],
})
.overrideModule(CircularModule)
.useModule(TestCircularModule)
.compile();
} catch (error) {
// Handle circular dependency errors
console.error("Circular dependency detected:", error.message);
}
// Handle missing module dependencies
@Module({
imports: [MissingDependency], // This might cause issues
providers: [TestService],
})
class ProblematicTestModule {}
// Better approach - provide all necessary dependencies
@Module({
providers: [
TestService,
{
provide: "MISSING_DEPENDENCY",
useValue: mockDependency,
},
],
})
class WellFormedTestModule {}import { ModuleDefinition } from "@nestjs/core/interfaces/module-definition.interface";
import { TestingModuleBuilder } from "./testing-module.builder";
interface OverrideModule {
useModule: (newModule: ModuleDefinition) => TestingModuleBuilder;
}
// ModuleDefinition is typically a class decorated with @Module()
type ModuleDefinition = Function;