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

NestJS控制器测试中CacheService依赖解析失败问题求助

问题:NestJS控制器Jest测试中CacheService依赖解析失败

问题背景

我使用Jest框架测试NestJS应用的CalculatorController,应用依赖MongoDB和自定义实现的CacheService(基于Nest内置缓存能力)。控制器逻辑为:接收表达式计算请求后,先传入CacheService检查缓存;若缓存未命中,由CacheService调用CalculatorService计算结果,再返回给控制器;随后调用HistoryService将结果存入数据库,最终返回客户端。

待测试的CalculatorController代码(calculator.controller.ts)

@Controller("/calculator")
export class CalculatorController {
  constructor(
    @Inject(HISTORY_SERVICE)
    private historyService: HistoryService,
    @Inject(CACHE_SERVICE)
    private readonly cacheService: CacheService,
  ) {}
  @Get()
  getResult(
    @Query() expressionDto: ExpressionDto,
  ): Promise<CalculationResultDto> {
    const { expression } = expressionDto;
    const response = this.cacheService
      .checkInCache(expression)
      .then((result) => {
        const dto = { result: `${result}`, expression };
        const historyItem = this.historyService.create(dto);
        return historyItem;
      });
    return response;
  }
}

测试代码(calculator.controller.spec.ts)

jest.mock('../calculator.service.ts');

let calculatorController: CalculatorController;
let calculatorService: CalculatorService;

beforeEach(async () => {
  const moduleRef = await Test.createTestingModule({
    imports: [HistoryModule],
    controllers: [CalculatorController],

    providers: [
      { useClass: CalculatorService, provide: CALCULATOR_SERVICE },
      {
        useClass: ExpressionCounterService,
        provide: EXPRESSION_COUNTER_SERVICE,
      },
      {
        useClass: RegExCreatorService,
        provide: REGEXP_CREATOR_SERVICE_INTERFACE,
      },
      { useClass: CacheService, provide: CACHE_SERVICE },
      { useClass: HistoryService, provide: HISTORY_SERVICE },
    ],
  }).compile();
  calculatorController =
    moduleRef.get<CalculatorController>(CalculatorController);
  calculatorService = moduleRef.get<CalculatorService>(CalculatorService);
  jest.clearAllMocks();
});

describe('getResult', () => {
  describe('when getResult is called', () => {
    beforeEach(async () => {
      await calculatorController.getResult(calculatorStub().request);
    });
    test('then it should call calculatorService', () => {
      expect(calculatorService.getResult).toBeCalledWith(
        calculatorStub().request.expression,
      );
    });
  });
});

实际模块配置(calculator.module.ts)

@Module({
  imports: [HistoryModule],
  controllers: [CalculatorController],
  providers: [
    { useClass: CalculatorService, provide: CALCULATOR_SERVICE },
    { useClass: ExpressionCounterService, provide: EXPRESSION_COUNTER_SERVICE },
    {
      useClass: RegExCreatorService,
      provide: REGEXP_CREATOR_SERVICE_INTERFACE,
    },
    { useClass: CacheService, provide: CACHE_SERVICE },
    { useClass: HistoryService, provide: HISTORY_SERVICE },
  ],
})
export class CalculatorModule {}

运行测试时的错误

Nest can't resolve dependencies of the CacheService (?, CALCULATOR_SERVICE). Please make sure that the argument CACHE_MANAGER at index [0] is available in the RootTestModule context.

问题原因及解决办法

错误核心是:CacheService的第一个依赖CACHE_MANAGER在测试模块中未被提供。实际项目中CalculatorModule应该导入了Nest的CacheModule(如CacheModule.register()),因此CACHE_MANAGER能被正常注入,但测试模块未配置该依赖。

解决步骤

1. 方案一:在测试模块中导入CacheModule

直接在测试模块的imports数组中添加CacheModule的注册配置,让Nest提供真实的CACHE_MANAGER实例:

const moduleRef = await Test.createTestingModule({
  imports: [HistoryModule, CacheModule.register({ isGlobal: true })], // 添加此行
  controllers: [CalculatorController],
  // ... 其他providers配置
}).compile();

2. 方案二:Mock所有外部依赖(推荐单元测试使用)

单元测试应隔离外部依赖,直接MockCACHE_MANAGER、CacheService和HistoryService,避免依赖真实缓存和数据库:
修改测试模块的providers配置:

providers: [
  { useClass: CalculatorService, provide: CALCULATOR_SERVICE },
  {
    useClass: ExpressionCounterService,
    provide: EXPRESSION_COUNTER_SERVICE,
  },
  {
    useClass: RegExCreatorService,
    provide: REGEXP_CREATOR_SERVICE_INTERFACE,
  },
  // Mock CacheService的实现
  {
    provide: CACHE_SERVICE,
    useValue: {
      checkInCache: jest.fn().mockResolvedValue('10'), // 模拟缓存返回结果
    },
  },
  // Mock HistoryService,避免MongoDB依赖
  {
    provide: HISTORY_SERVICE,
    useValue: {
      create: jest.fn().mockResolvedValue({ id: 'test-id', result: '10', expression: '2+8' }),
    },
  },
  // Mock CacheService依赖的CACHE_MANAGER
  {
    provide: CACHE_MANAGER,
    useValue: {
      get: jest.fn(),
      set: jest.fn(),
    },
  },
],

同时移除imports中的HistoryModule,因为已经Mock了HISTORY_SERVICE,无需导入整个模块。

修改后的完整测试示例

jest.mock('../calculator.service.ts');

let calculatorController: CalculatorController;
let calculatorService: CalculatorService;
let cacheService: jest.Mocked<CacheService>;
let historyService: jest.Mocked<HistoryService>;

beforeEach(async () => {
  const moduleRef = await Test.createTestingModule({
    controllers: [CalculatorController],
    providers: [
      { useClass: CalculatorService, provide: CALCULATOR_SERVICE },
      {
        useClass: ExpressionCounterService,
        provide: EXPRESSION_COUNTER_SERVICE,
      },
      {
        useClass: RegExCreatorService,
        provide: REGEXP_CREATOR_SERVICE_INTERFACE,
      },
      {
        provide: CACHE_SERVICE,
        useValue: {
          checkInCache: jest.fn().mockResolvedValue('10'),
        },
      },
      {
        provide: HISTORY_SERVICE,
        useValue: {
          create: jest.fn().mockResolvedValue({ id: '1', result: '10', expression: '2+8' }),
        },
      },
      {
        provide: CACHE_MANAGER,
        useValue: { get: jest.fn(), set: jest.fn() },
      },
    ],
  }).compile();

  calculatorController = moduleRef.get<CalculatorController>(CalculatorController);
  calculatorService = moduleRef.get<CalculatorService>(CALCULATOR_SERVICE);
  cacheService = moduleRef.get(CACHE_SERVICE);
  historyService = moduleRef.get(HISTORY_SERVICE);
  jest.clearAllMocks();
});

describe('getResult', () => {
  const testExpression = '2+8';
  const testResult = '10';

  it('should check cache first and save result to history', async () => {
    const result = await calculatorController.getResult({ expression: testExpression });

    expect(cacheService.checkInCache).toHaveBeenCalledWith(testExpression);
    expect(historyService.create).toHaveBeenCalledWith({ result: testResult, expression: testExpression });
    expect(result).toEqual({ id: '1', result: testResult, expression: testExpression });
  });

  it('should call calculatorService when cache misses', async () => {
    // 模拟缓存未命中逻辑
    cacheService.checkInCache.mockImplementation(async () => {
      const calcResult = await calculatorService.getResult(testExpression);
      return calcResult;
    });
    calculatorService.getResult.mockResolvedValue(testResult);

    await calculatorController.getResult({ expression: testExpression });

    expect(calculatorService.getResult).toHaveBeenCalledWith(testExpression);
  });
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 17:25:31