努力将我的模拟 Runnable 注入到我的服务调用中

问题描述 投票:0回答:1

对于上下文,我使用 Resilience4j 来处理异常并调用重试。这是通过 RetryService 完成的。

在此函数中,

callRunnableWithRetry()
有两个参数。第一个是字符串,第二个是 Runnable。我设计了这样的函数,使其模块化,以便任何人都可以处理他们想要处理的任何代码块。

@Test
void testRetry(){
    RetryService retryServiceSpy = spy(retryService);

    ObjectA obj = new ObjectA("test-value");

    Runnable mockRunnable = mock(Runnable.class);

    doThrow(new RuntimeException("Simulated exception"))
        .doThrow(new RuntimeException("Simulated exception"))
        .doNothing()
        .when(mockRunnable).run();

    doAnswer(invocation -> {
        String configName = invocation.getArgument(0);
        Runnable actualRunnable = invocation.getArgument(1);
        Retry retry = retryServiceSpy.getRetry(configName);

        CheckedRunnable checkedRunnable = Retry.decorateCheckedRunnable(retry, actualRunnable::run);

        try {
            checkedRunnable.run();
        } catch (Exception ignored) {
            log.error("error: {}", ignored)
        }
        return null;
    }).when(retryService).callRunnableWithRetry("test", mockRunnable);


    serviceA.getData(obj);

    verify(mockRunnable, times(3)).run();
}

问题

正如你所看到的,我正在创建一个模拟,它抛出异常两次然后什么都不做。我需要这个,这样我就可以在我通过resilience4j传递给

callRunnableWithRetry()
的可运行对象上触发我的重试功能。

当我使用 verify 来查看 runnable.run() 是否实际上被调用时 3x Mockito 给了我这个错误。

Wanted but not invoked:
runnable.run();
-> at [redacted]
Actually, there were zero interactions with this mock.

所以我还尝试创建我的 Runnable 的间谍,然后将其注入到我的 doAnswer() 中,这样我就可以确保我的模拟 Runnable 实际上被应用在我的

serviceA.getData()
调用中,但即使这样也没有工作;但如果有人可以让它工作,请分享。

背景

如果好奇的话,这是 RetryService

@Service
@Sl4j
public class RetryService {

    private static final String RETRY_LOG_MESSAGE = "%s %s from service: %s" +  
  "\nattempts made: %s" +  
  "\nexception:\n```%s```";  
  
    public Retry getRetry(String retryName) {  
      //process to acquire Reslience4j retry object
      ...
      return retry;  
    }  
      
    private void handleRetryEvents(Retry retry, String action) {  
      retry.getEventPublisher()  
        .onSuccess(event -> logEvent(action, false, event))  
        .onRetry(event -> logEvent(action, false, event))  
        .onError(event -> logEvent(action, true, event));  
    }  
      
    private void logEvent(String action, boolean isAlert, RetryEvent event) {  
      //maps data to RETRY_LOG_MESSAGE string
      ...
    }  
      
    public CheckedRunnable callRunnableWithRetry(String configName, Runnable runnableFunc) {  
      Retry retry = getRetry(configName);  
      handleRetryEvents(retry, "read");  
      return decorateCheckedRunnable(retry, () -> runnableFunc.run());  
    }
}

这就是我的实现代码在 ServiceA 中的样子

@Service
@Sl4j
public class ServiceA {
    private final RetryService retryService;

    public ServiceA(RetryService retryService){
        this.retryService = retryService;
    }
    public void getData(ObjectA obj) {  
      try {  
        //processes data
        ...  
        retryService.callRunnableWithRetry(obj.getName(), () -> {  
          log.debug("Name: {}", obj.getName());  
        }).run();  
      } catch (Throwable e) {  
        log.error("error: {}", e);
      }  
    }
}
java mockito runnable functional-interface
1个回答
0
投票

正如评论中所解释的,您没有对您的

mockRunnable
做任何事情。让我们详细看看吧。

doAnswer(invocation -> {
    // irrelevant
    return null;
}).when(retryService).callRunnableWithRetry("test", mockRunnable);

上面的状态:callRunnableWithRetry上的if(或

when
)方法
retryService
被调用,参数为
"test"
和确切的
mockRunnable
实例(
Runnable
不实现
equals()
和Mockito模拟)实例始终仅通过身份/引用进行比较),然后调用答案代码块。 以这种方式存根方法不会调用该方法,也不会替换该方法的实际参数。

您的 SUT

callRunnableWithRetry
的生产代码实际上如何调用
ServiceA

retryService.callRunnableWithRetry(obj.getName(), () -> {  
  log.debug("Name: {}", obj.getName());  
}).run();

它的第二个参数是

() -> { log.debug("Name: {}", obj.getName()); }
,这很明显不是你的
mockRunnable
实例,所以存根答案不匹配,因此没有被执行。

我也不太清楚为什么你在存根答案中重新实现

RetryService
的生产逻辑。您想测试哪个课程?如果你想测试
RetryService
,请在测试中去掉
ServiceA
并直接测试RetryService。如果你想测试
ServiceA
,只需验证
callRunnableWithRetry
方法是否已被调用(也许它的返回值已运行?)。请注意,方法名称
callRunnableWithRetry
本身非常具有误导性,因为该方法实际上并不调用可运行对象 - 它返回一个新的可运行对象,需要调用该新可运行对象才能执行原始可运行对象。

创建

Runnable
类的 Mockito 模拟实例不会神奇地用此模拟替换所有 Runnable 实例,也不会使该实例可供您的对象使用,除非您在代码中明确这样做。此问题的类似变体和可能的解决方案在以下内容中进行了解释:为什么在执行单元测试时未调用我的模拟方法?

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.