Testing
@neststack/config is designed to be easy to test. The key pattern: use envSource to inject fake environment variables and NestStackConfigModule.reset() to clear state between tests.
Setup
Tests use Vitest with the @nestjs/testing module:
import { describe, it, expect, beforeEach } from 'vitest';
import { Test } from '@nestjs/testing';
import { NestStackConfigModule, ConfigService, defineConfig } from '@neststack/config';
import { z } from 'zod';Reset Between Tests
The ConfigStore is a static singleton. You must reset it before each test to prevent state leaking between tests:
beforeEach(() => {
NestStackConfigModule.reset();
});Without this, the second test will fail with “Configuration namespace X is already registered.”
Inject Fake Environment Variables
Use the envSource option to provide test values without modifying process.env:
const testConfig = defineConfig({
namespace: 'database',
schema: z.object({
host: z.string(),
port: z.number().default(5432),
}),
load: ({ env }) => ({
host: env.getString('DB_HOST'),
port: env.getNumber('DB_PORT', 5432),
}),
});
it('should read config from envSource', async () => {
const module = await Test.createTestingModule({
imports: [
NestStackConfigModule.forRoot({
configs: [testConfig],
envSource: {
DB_HOST: 'test-host',
DB_PORT: '9999',
},
}),
],
}).compile();
const config = module.get(ConfigService);
expect(config.get('database.host')).toBe('test-host');
expect(config.get('database.port')).toBe(9999);
});Test Overrides
Use overrides to test specific scenarios without changing environment variables:
it('should apply overrides', async () => {
const module = await Test.createTestingModule({
imports: [
NestStackConfigModule.forRoot({
configs: [testConfig],
envSource: { DB_HOST: 'original' },
overrides: {
database: { host: 'overridden' },
},
}),
],
}).compile();
const config = module.get(ConfigService);
expect(config.get('database.host')).toBe('overridden');
});Test Validation Errors
Verify that invalid config is caught:
it('should throw on invalid config', async () => {
await expect(
Test.createTestingModule({
imports: [
NestStackConfigModule.forRoot({
configs: [testConfig],
envSource: {}, // DB_HOST is required
}),
],
}).compile(),
).rejects.toThrow('Config validation failed');
});Test Feature Modules
it('should register feature config', async () => {
const featureConfig = defineConfig({
namespace: 'feature',
schema: z.object({ enabled: z.boolean().default(true) }),
});
const module = await Test.createTestingModule({
imports: [
NestStackConfigModule.forRoot({ configs: [] }),
NestStackConfigModule.forFeature(featureConfig),
],
}).compile();
const config = module.get(ConfigService);
expect(config.get('feature.enabled')).toBe(true);
});Test explain()
it('should explain value sources', async () => {
const module = await Test.createTestingModule({
imports: [
NestStackConfigModule.forRoot({
configs: [testConfig],
envSource: { DB_HOST: 'loaded-host' },
}),
],
}).compile();
const config = module.get(ConfigService);
const explanation = config.explain('database.host');
expect(explanation.source).toBe('loader');
expect(explanation.isSecret).toBe(false);
expect(explanation.value).toBe('loaded-host');
});Coverage Requirements
The @neststack/config package enforces 100% code coverage on lines, branches, functions, and statements. When adding new features, ensure all code paths are tested:
pnpm nx test config --coverageThe coverage report shows exactly which lines are uncovered.