NestJS多模块复用控制器Jest测试依赖注入冲突问题
问题场景
开发时定义了继承自MyAbstractService的三个服务ServiceA/ServiceB/ServiceC,同时三个模块ModuleA/ModuleB/ModuleC复用同一个MyController,每个模块都以COMMON_SERVICE为令牌注入对应服务。生产环境启动应用时注入符合预期,但Jest测试中通过Test.createTestingModule导入MainModule并select(ModuleB)获取控制器时,注入的却是ServiceC而非ServiceB,尝试useFactory也未解决。
核心原因
Nest的DI容器在测试环境中,当多个模块注册同一个令牌(COMMON_SERVICE)时,后注册的provider会覆盖之前的。由于MainModule按顺序导入ModuleA→ModuleB→ModuleC,ServiceC的provider最后被注册到全局容器。同时MyController默认是单例作用域,多个模块复用同一个控制器类时,最终只会存在一个控制器实例,该实例会使用全局容器中最后注册的COMMON_SERVICE实现。
解决方案
方案1:给控制器添加模块级作用域
为MyController添加@Scope('module')装饰器,让每个模块拥有独立的控制器实例,确保每个模块的控制器注入对应模块的服务:
import { Controller, Inject, Scope } from '@nestjs/common'; @Controller() @Scope('module') // 添加模块级作用域 export class MyController { constructor( @Inject("COMMON_SERVICE") private readonly commonService: MyAbstractService, ) { commonService.doSomething(); } }
注:也可根据业务需求选择
@RequestScope()(请求级实例)或@Transient()(每次获取新实例),核心是打破单例复用。
方案2:每个模块使用独立控制器子类
避免复用同一个控制器类,为每个模块创建控制器子类,确保每个模块的控制器属于自身模块上下文:
// 基类控制器(提取通用逻辑) export class BaseMyController { constructor( @Inject("COMMON_SERVICE") private readonly commonService: MyAbstractService, ) { commonService.doSomething(); } } // ModuleA专属控制器 @Controller() export class ModuleAController extends BaseMyController {} // ModuleB专属控制器 @Controller() export class ModuleBController extends BaseMyController {} // ModuleC专属控制器 @Controller() export class ModuleCController extends BaseMyController {}
修改各模块配置,替换为对应专属控制器:
@Module({ controllers: [ModuleAController], providers: [ { provide: "COMMON_SERVICE", useClass: ServiceA, }, ] }) export class ModuleA {} // ModuleB、ModuleC同理替换控制器
测试时直接获取目标模块的专属控制器:
const controller = module .select(ModuleB) .get<ModuleBController>(ModuleBController);
方案3:测试时单独导入目标模块
不需要导入整个MainModule,只导入需要测试的ModuleB,彻底避免其他模块的provider干扰:
const module = await Test.createTestingModule({ imports: [ModuleB], }).compile(); const controller = module.get<MyController>(MyController);
内容的提问来源于stack exchange,提问作者rnoodle

