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

CompletableFuture Spy测试无限运行及Mockito类型错误求助

问题排查与解决方案

核心问题分析

1. Mockito WrongTypeOfReturnValue 错误

你使用when(spy.get()).thenThrow()语法对CompletableFuture的spy对象进行stub时,Mockito会先执行真实的get()方法。CompletableFuture内部实现中,get()的调用可能触发toString相关方法,导致Mockito在处理stub逻辑时出现类型不匹配——错误地尝试将PatientTreatmentDetails返回给原本应返回String的toString方法。根据报错提示,spy对象的stub必须优先使用doThrow/doReturn系列方法,避免执行真实方法带来的副作用。

2. 测试无限执行

业务代码先通过CompletableFuture.allOf().join()等待所有异步任务完成,再调用get()获取结果。如果spy的CompletableFuture被错误stub,会干扰其完成状态,导致join()无法正常结束;另外,当get()抛出InterruptedException后,代码调用Thread.currentThread().interrupt()中断当前线程,可能引发JUnit测试线程的异常处理逻辑,进而导致无限阻塞。

修复方案

方案1:用doThrow替换when...thenThrow(修正spy的stub方式)

修改测试中对CompletableFuture spy的stub逻辑,避免执行真实的get()方法:

@Test
void interruptedExceptionThrownByCompletableFutures() throws InterruptedException, ExecutionException {
    // given
    when(patientsClient.getPatientTreatmentDetails(RPT_TOKEN, "123", CLINICAL_ID))
            .thenReturn(createPatientTreatmentDetails());
    
    // 使用doThrow stub spy的get方法,跳过真实方法执行
    CompletableFuture<List<HDTreatmentOrder>> hdFuture = spy(CompletableFuture.completedFuture(Collections.emptyList()));
    doThrow(new InterruptedException()).when(hdFuture).get();

    CompletableFuture<List<PDTreatmentOrder>> pdFuture = spy(CompletableFuture.completedFuture(Collections.emptyList()));
    doThrow(new InterruptedException()).when(pdFuture).get();

    when(hdService.getData(any(), any())).thenReturn(hdFuture);
    when(pdService.getData(any(), any())).thenReturn(pdFuture);

    when(tDService.getData(RPT_TOKEN, "123", filter))
            .thenReturn(CompletableFuture.completedFuture(new PatientTreatmentDetails()));
    
    // then
    assertThrows(PatientOrderException.class, () -> treatmentOrderService.getTreatmentOrders("123", filter, RPT_TOKEN));
}

方案2:直接Mock CompletableFuture(更简洁可靠)

无需spy真实的CompletableFuture,直接mock并stub所需方法,彻底避免真实对象的副作用:

@Test
void interruptedExceptionThrownByCompletableFutures() throws InterruptedException, ExecutionException {
    // given
    when(patientsClient.getPatientTreatmentDetails(RPT_TOKEN, "123", CLINICAL_ID))
            .thenReturn(createPatientTreatmentDetails());
    
    // Mock CompletableFuture,stub get()抛出异常,join()正常返回(避免allOf().join()阻塞)
    CompletableFuture<List<HDTreatmentOrder>> hdFuture = mock(CompletableFuture.class);
    doThrow(new InterruptedException()).when(hdFuture).get();
    doNothing().when(hdFuture).join();

    CompletableFuture<List<PDTreatmentOrder>> pdFuture = mock(CompletableFuture.class);
    doThrow(new InterruptedException()).when(pdFuture).get();
    doNothing().when(pdFuture).join();

    when(hdService.getData(any(), any())).thenReturn(hdFuture);
    when(pdService.getData(any(), any())).thenReturn(pdFuture);

    when(tDService.getData(RPT_TOKEN, "123", filter))
            .thenReturn(CompletableFuture.completedFuture(new PatientTreatmentDetails()));
    
    // then
    assertThrows(PatientOrderException.class, () -> treatmentOrderService.getTreatmentOrders("123", filter, RPT_TOKEN));
}

额外检查:Setup方法的潜在问题

报错栈显示错误发生在setup方法第84行,需检查该方法中是否存在类似的spy对象stub错误(比如错误地用when(spy.toString()).thenReturn(...)返回非String类型),同样需要将这类stub替换为doReturn语法。

验证修复

修改后单独运行测试用例,确认:

  • Mockito的WrongTypeOfReturnValue错误消失
  • 测试用例正常结束,无无限阻塞
  • 成功触发PatientOrderException,符合断言预期

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 02:25:45