ConfigModel: Uses featureConfigurationClass with DI
Overview
These tests validate nestjs-mod ConfigModel: configuration transformation, input validation, and error contract on invalid parameters.
What We're Testing
- Class-Based Configuration: Ensures configuration can be provided as an injectable class
- Dependency Injection in Config: Verifies config classes can inject and use other services
- Factory Pattern: Tests the async factory pattern for feature configuration
- Service Integration: Confirms injected services work correctly within config classes
GitHub Reference
- File: utils.spec.ts
- Lines: 619-703
Setup Code
This test creates a config class that injects a helper service:
import { Injectable } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { ConfigModel, ConfigModelProperty } from '../config-model/decorators';
import { InjectableFeatureConfigurationType } from './types';
import { createNestModule, getNestModuleDecorators } from './utils';
@ConfigModel()
class DatabaseFeatureConfig {
@ConfigModelProperty({ default: 'localhost' })
host!: string;
@ConfigModelProperty({ default: 5432 })
port!: number;
@ConfigModelProperty({ default: 'mydb' })
database!: string;
}
// Service to be injected into config class
@Injectable()
class ConfigHelper {
getDefaultHost() {
return 'db.example.com';
}
}
const { InjectFeatures } = getNestModuleDecorators({
moduleName: 'DatabaseModule',
});
@Injectable()
class DatabaseService {
constructor(
@InjectFeatures()
private readonly featureConfigs: InjectableFeatureConfigurationType<DatabaseFeatureConfig>[],
) {}
getFeatureConfigs() {
return this.featureConfigs;
}
}
const { DatabaseModule } = createNestModule({
moduleName: 'DatabaseModule',
featureConfigurationModel: DatabaseFeatureConfig,
sharedProviders: [DatabaseService, ConfigHelper],
});
// Config class with DI - constructor can inject dependencies
@Injectable()
class DatabaseConfigClass {
constructor(private readonly configHelper: ConfigHelper) {}
host = this.configHelper.getDefaultHost();
port = 5432;
database = 'production_db';
}
Test Code
it('should use featureConfigurationClass with DI', async () => {
const { AppModule } = createNestModule({
moduleName: 'AppModule',
imports: [
DatabaseModule.forFeatureAsync({
featureModuleName: 'MainDatabase',
featureConfigurationClass: DatabaseConfigClass,
inject: [ConfigHelper],
}),
],
});
const moduleRef: TestingModule = await Test.createTestingModule({
imports: [DatabaseModule.forRoot(), AppModule.forRoot()],
providers: [ConfigHelper],
}).compile();
const databaseService = moduleRef.get(DatabaseService);
const featureConfigs = databaseService.getFeatureConfigs();
expect(featureConfigs).toBeDefined();
expect(featureConfigs.length).toBeGreaterThanOrEqual(1);
const config = featureConfigs[0];
expect(config.featureModuleName).toBe('MainDatabase');
expect(config.featureConfiguration).toMatchObject({
host: 'db.example.com', // From injected ConfigHelper
port: 5432,
database: 'production_db',
});
});
What This Test Does
-
Creates a ConfigHelper service that provides default configuration values
-
Creates a DatabaseConfigClass that:
- Is decorated with
@Injectable()to support DI - Injects
ConfigHelperin its constructor - Uses the injected helper to compute the
hostvalue - Sets other properties directly
- Is decorated with
-
Creates DatabaseModule with:
featureConfigurationModeldefining the config structure- Both
DatabaseServiceandConfigHelperas shared providers
-
Creates AppModule that:
- Uses
forFeatureAsyncwithfeatureConfigurationClass - Specifies which dependencies to
inject([ConfigHelper]) - Names the feature module 'MainDatabase'
- Uses
-
Compiles the module and retrieves
DatabaseService -
Verifies that:
- Feature configs are defined
- The configuration was instantiated from the class
- The
hostvalue came from the injectedConfigHelper('db.example.com') - Other values match the class properties
Key Points
- This test locks the behavior contract of the target nestjs-mod block in this scenario.
- Assertions protect integration boundaries and reduce regression risk when internals evolve.