NestJS 测试中环境变量未加载的解决方案

15次阅读

NestJS 测试中环境变量未加载的解决方案

在 nestjs 单元测试中,`configservice` 返回 `undefined` 是因测试模块未导入 `configmodule`,导致 `.env` 文件未被加载;需显式注册配置模块或使用 mock 替代。

在你的测试代码中,虽然 appService 依赖 Configservice,但 Test.createTestingModule() 仅提供了 ConfigService 实例(通过 providers: [ConfigService, AppService]),并未初始化 ConfigModule。而 ConfigModule.forRoot() 才是真正负责读取 .env、解析环境变量并为 ConfigService 注入配置数据的核心模块——它会自动调用 dotenv.config() 并构建配置缓存。单独提供 ConfigService 类(无模块上下文)只会创建一个空实例,其内部配置存储为空,因此 get(‘TEST_VAR’) 必然返回 undefined。

✅ 正确做法:在测试模块中导入 ConfigModule

修改 app.controller.spec.ts,显式导入 ConfigModule 并启用 .env 加载:

import { Test, TestingModule } from '@nestjs/testing'; import { ConfigModule } from '@nestjs/config'; // ? 引入 ConfigModule import { AppController } from './app.controller'; import { AppService } from './app.service';  describe('AppController', () => {   let appController: AppController;    beforeEach(async () => {     const app: TestingModule = await Test.createTestingModule({       imports: [         ConfigModule.forRoot({ // ? 启用配置模块(自动加载 .env)           isGlobal: true, // 推荐设为 true,避免在每个模块重复导入         }),       ],       controllers: [AppController],       providers: [AppService], // ❌ 不再提供 ConfigService —— 由 ConfigModule 自动提供     }).compile();      appController = app.get(AppController);   });    describe('Test variable', () => {     it('should return "ASD"', () => {       expect(appController.getTest()).toBe('ASD');     });   }); });

✅ 关键点:imports: [ConfigModule.forRoot(…)] 是必须项;isGlobal: true 可确保 ConfigService 在整个测试模块中可用,无需手动 provide;移除 providers: [ConfigService],避免与 ConfigModule 提供的实例冲突。

⚠️ 注意事项

  • .env 文件位置:必须位于 node.js 进程工作目录下(通常是项目根目录),ConfigModule.forRoot() 默认从此处读取。运行 npm run test 时,确保终端在项目根目录执行。
  • 环境变量优先级:.env 中的值可被系统环境变量覆盖(如 TEST_VAR=XYZ npm run test),调试时可用 console.log(process.env.TEST_VAR) 验证原始值。
  • 生产环境提示:forRoot() 在测试中启用 .env 是安全的;但在生产部署中,建议结合 envFilePath 显式指定路径(如 envFilePath: [‘.env.production’, ‘.env’])并禁用 ignoreEnvFile: true。

? 更推荐:使用 Mock 替代真实配置(进阶实践)

为提升测试隔离性与性能,官方及社区更推荐对 ConfigService 进行 Mock,而非加载真实 .env:

beforeEach(async () => {   const configServiceMock = {     get: jest.fn((key: string) => {       if (key === 'TEST_VAR') return 'ASD';       return undefined;     }),   };    const app: TestingModule = await Test.createTestingModule({     controllers: [AppController],     providers: [       AppService,       {         provide: ConfigService,         useValue: configServiceMock,       },     ],   }).compile();    appController = app.get(AppController); });

这种方式完全解耦测试与文件 I/O,响应更快,且可灵活模拟不同环境场景(如缺失变量、类型转换异常等)。

综上,undefined 根源在于测试模块缺少 ConfigModule 的引导逻辑;修复只需补全 imports,或采用更可控的 Mock 策略——二者皆可,但后者更符合单元测试“快速、独立、可预测”的设计原则。

text=ZqhQzanResources