Nest.js单体仓库从共享Lib导入服务时E2E测试依赖注入失败
问题场景
在Nest.js单体仓库中,使用Test.createTestingModule()执行E2E测试时,从共享libs目录导入服务会出现依赖注入失败;但从测试文件相对路径的嵌套目录导入完全相同的模块和服务时,测试可正常运行。
以@nest/config模块为例,测试应用与从libs导入的模块/服务无法共享上下文:从libs导入时,MyConfigService中configService.get('abcd')输出undefined;从嵌套目录导入则输出预期的Mock值。
仓库结构
mono-root - libs - config-test - node_modules (包含@nest及typeorm相关依赖) - src - 模块与服务文件 - apps - myapp - node_modules (包含@nest及typeorm相关依赖) - src - main.ts - e2e-spec.ts - config-test - 与/libs中完全相同的模块和服务文件,无node_modules
Libs目录核心代码
模块文件(libs/src/config-module.ts)
import { Module } from "@nestjs/common"; import { MyConfigService } from "./config-service"; import { ConfigModule } from "@nestjs/config"; @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, }), ], providers: [MyConfigService], exports: [MyConfigService], }) export class MyConfigModule {}
服务文件(libs/src/config-service.ts)
import { Injectable } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; @Injectable() export class MyConfigService { constructor(private configService: ConfigService) { console.log("MyConfigService", this.configService.get<string>("abcd")); } }
E2E测试代码(config.e2e-spec.ts)
import { Test } from "@nestjs/testing"; import { MyConfigModule, MyConfigService } from "@spotlightsportsgroup/nest"; // import { MyConfigModule, MyConfigService } from "./config-test/index"; import { ConfigService, ConfigModule } from "@nestjs/config"; describe("SharedLib", () => { let myConfigService: MyConfigService; let configService: ConfigService; beforeAll(async () => { const moduleRef = await Test.createTestingModule({ imports: [MyConfigModule, ConfigModule.forRoot()], providers: [MyConfigService], }) .overrideProvider(ConfigService) .useValue({ get: jest.fn((value) => { return "mocked:" + value; }), }) .compile(); myConfigService = moduleRef.get<MyConfigService>(MyConfigService); configService = moduleRef.get<ConfigService>(ConfigService); }); });
测试现象对比
从嵌套目录导入时日志输出
console.log MyConfigService mocked:abcd at new MyConfigService (config-test/config-test/config-service.ts:8:13) console.log MyConfigService mocked:abcd
从libs目录导入时日志输出
console.log MyConfigService undefined at new MyConfigService (config-test/config-test/config-service.ts:8:13) console.log MyConfigService undefined
原因与解决方案
核心原因
问题根源是重复依赖实例隔离:libs目录下的config-test单独安装了@nestjs/*依赖,导致测试时,libs里的MyConfigService依赖的是libs/node_modules中的ConfigService类实例,而测试模块中overrideProvider覆盖的是apps/node_modules中的ConfigService实例,两者属于不同模块的类,无法共享Mock上下文。
解决步骤
删除libs目录下的node_modules
单体仓库的共享库无需单独安装依赖,所有依赖统一由根目录或apps目录管理,彻底避免重复依赖导致的实例隔离问题。配置Monorepo依赖hoist
如果使用Yarn Workspaces、Nx或Lerna等Monorepo工具,确保开启依赖hoist功能,将所有依赖提升到根目录,让仓库内所有代码共享同一套Nest.js依赖实例。简化测试模块配置
无需在测试模块中重复导入ConfigModule.forRoot()和MyConfigService,直接导入MyConfigModule即可,再通过overrideProvider覆盖全局ConfigService:beforeAll(async () => { const moduleRef = await Test.createTestingModule({ imports: [MyConfigModule], }) .overrideProvider(ConfigService) .useValue({ get: jest.fn((value) => "mocked:" + value), }) .compile(); myConfigService = moduleRef.get<MyConfigService>(MyConfigService); configService = moduleRef.get<ConfigService>(ConfigService); });因为
MyConfigModule已导入全局ConfigModule,重复导入会导致多次初始化,干扰依赖注入逻辑。校验TypeScript路径映射
检查项目tsconfig.json中的paths配置,确保@spotlightsportsgroup/nest正确指向libs目录的源码或编译产物,避免路径解析错误导致的代码重复加载。
内容的提问来源于stack exchange,提问作者Laurence Fass

