如何测试依赖多服务的NestJS Controller?问题排查与解决
问题分析与解决
核心问题点
- Mock的Token不匹配:控制器注入依赖时使用的是
@Inject(HISTORY_SERVICE)、@Inject(CACHE_SERVICE)这类自定义Token,而非类本身,直接提供CacheService类无法被Nest识别为对应依赖。 - Mock方法返回值错误:
mockResolvedValue无需手动包裹Promise,它本身会自动返回Promise;另外createMock()生成的默认mock方法可能返回非Promise值,导致.then()调用报错。
修复步骤
步骤1:使用正确的Token提供Mock
所有注入的依赖必须对应控制器中@Inject()装饰器里的Token,而非类名:
- 用
HISTORY_SERVICE作为HistoryService的注入标识 - 用
CACHE_SERVICE作为CacheService的注入标识 - 用
CALCULATOR_SERVICE作为CalculatorService的注入标识
步骤2:正确配置Mock的返回值
mockResolvedValue直接传入目标值(如null或模拟响应对象),它会自动包装为Promise- 同步方法直接用
mockReturnValue返回对应值,异步方法确保返回Promise类型
完整修复后的测试代码
let calculatorController: CalculatorController; let historyService: jest.Mocked<HistoryService>; let cacheService: jest.Mocked<CacheService>; let calculatorService: jest.Mocked<CalculatorService>; beforeEach(async () => { const moduleRef = await Test.createTestingModule({ controllers: [CalculatorController], providers: [ { provide: HISTORY_SERVICE, useValue: { create: jest.fn().mockResolvedValue({ id: 'mock-id', expression: '1+1', result: 2, createdAt: new Date(), }), }, }, { provide: CACHE_SERVICE, useValue: { checkInCache: jest.fn().mockResolvedValue(null), setToCache: jest.fn().mockResolvedValue({ expression: '1+1', result: 2, }), }, }, { provide: CALCULATOR_SERVICE, useValue: { getResult: jest.fn().mockReturnValue(2), }, }, ], }).compile(); calculatorController = moduleRef.get<CalculatorController>(CalculatorController); historyService = moduleRef.get(HISTORY_SERVICE); cacheService = moduleRef.get(CACHE_SERVICE); calculatorService = moduleRef.get(CALCULATOR_SERVICE); jest.clearAllMocks(); }); describe('Calculator Controller', () => { it('should be defined', () => { expect(calculatorController).toBeDefined(); }); it('should return a Promise from getResult', async () => { const result = calculatorController.getResult({ expression: '1+1' }); expect(result).toBeInstanceOf(Promise); await expect(result).resolves.toMatchObject({ expression: '1+1', result: 2 }); }); it('should fetch from cache first, then calculate and save if cache miss', async () => { cacheService.checkInCache.mockResolvedValue(null); await calculatorController.getResult({ expression: '1+1' }); expect(cacheService.checkInCache).toHaveBeenCalledWith('1+1'); expect(calculatorService.getResult).toHaveBeenCalledWith('1+1'); expect(historyService.create).toHaveBeenCalledWith({ expression: '1+1', result: 2 }); expect(cacheService.setToCache).toHaveBeenCalled(); }); });
额外优化建议
- 避免混合使用
useMocker和手动Mock,手动Mock在复杂场景下更可控 - 明确配置每个Mock方法的返回值,保证测试逻辑的可预测性
- 增加对依赖方法调用的断言,验证控制器业务逻辑的正确性
内容的提问来源于stack exchange,提问作者tresor13
相关产品推荐
相关产品推荐

