You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Nest.js单体仓库从共享Lib导入服务时E2E测试依赖注入失败

Nest.js 单体仓库E2E测试共享Lib依赖注入失败问题

问题场景

在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上下文。

解决步骤

  1. 删除libs目录下的node_modules
    单体仓库的共享库无需单独安装依赖,所有依赖统一由根目录或apps目录管理,彻底避免重复依赖导致的实例隔离问题。

  2. 配置Monorepo依赖hoist
    如果使用Yarn Workspaces、Nx或Lerna等Monorepo工具,确保开启依赖hoist功能,将所有依赖提升到根目录,让仓库内所有代码共享同一套Nest.js依赖实例。

  3. 简化测试模块配置
    无需在测试模块中重复导入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,重复导入会导致多次初始化,干扰依赖注入逻辑。

  4. 校验TypeScript路径映射
    检查项目tsconfig.json中的paths配置,确保@spotlightsportsgroup/nest正确指向libs目录的源码或编译产物,避免路径解析错误导致的代码重复加载。


内容的提问来源于stack exchange,提问作者Laurence Fass

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 20:20:21