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

如何测试依赖多服务的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 21:15:34