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
相关产品推荐
相关产品推荐

