Java Mockito验证在可调用对象上未失败

Java Mockito验证在可调用对象上未失败,java,unit-testing,mocking,mockito,callable,Java,Unit Testing,Mocking,Mockito,Callable,对于对可调用参数执行操作的方法,我有以下代码: public static <T> T queryWithRetry(Callable<T> query, int maxTries, int retryIntervalInMilliseconds) throws MongoServiceException, InterruptedException { int tries = MAX_TRIES; while (tries-- > 0) {

对于对可调用参数执行操作的方法,我有以下代码:

public static <T> T queryWithRetry(Callable<T> query, int maxTries, int retryIntervalInMilliseconds) throws MongoServiceException, InterruptedException {
    int tries = MAX_TRIES;
    while (tries-- > 0) {
      try {
        return query.call();
      } catch (TimeoutException e) {
        LOGGER.debug(String.format("Query timed out. Retrying attempt %d/%d", MAX_TRIES - tries, MAX_TRIES));
        Thread.sleep(RETRY_INTERVAL_IN_MILLISECONDS);
        continue;
    }
    throw new RandomException();
  }
Mockito代码成功地测试了该方法是否抛出了
RandomException
,但错误地说测试通过了

这个测试应该失败,因为我写了Mockito应该验证
mockCallable.call()
最多执行一次,但据我所知,它被称为
MAX\u trys
次(设置为3)


有人能解释一下这种行为,并就如何正确测试调用
mockCallable.call()
的次数给出建议吗?

您的测试有很多问题。以下是您想要的:

private static final int MAX_TRIES = 3;
private static final int RETRY_INTERVAL_IN_MILLISECONDS = 100;

@Mock
private Callable<String> mockCallable;

@Test
public void testQueryConfigThrowsRandomExceptionOnTimeout() throws Exception {
    when(mockCallable.call()).thenThrow(new IllegalArgumentException("timeout"));
    try {
        queryWithRetry(mockCallable, MAX_TRIES, RETRY_INTERVAL_IN_MILLISECONDS);
        fail("should have thrown");
    } catch (RuntimeException re) {
        // as expected
    }
    verify(mockCallable, Mockito.times(3)).call();
}
private static final int MAX_trys=3;
私有静态最终整数重试间隔(毫秒)=100;
@嘲弄
私有可调用可调用;
@试验
public void TestQueryConfigHrowsRandomExceptionOnTimeout()引发异常{
when(mockCallable.call()).ThentThrow(新的IllegalArgumentException(“超时”);
试一试{
queryWithRetry(可模拟调用,最大重试次数,重试间隔,以毫秒为单位);
失败(“应该抛出”);
}捕获(运行时异常re){
//果然
}
验证(mockCallable,Mockito.times(3)).call();
}
(请注意:我更改了测试的异常类型;但这应该是显而易见的)

那么,你做错了什么:

  • 我建议使用
    @RunWith(MockitoJUnitRunner.class)
    ,然后只使用@Mock注释。您有@Mock annotation加上一个为同一字段调用Mockito.Mock()的设置方法。这是多余的。将注释与@RunWith一起使用,或者在安装方法中调用MockitoAnnotations.initMocks()
  • 与模拟对象交互后,必须调用
    verify()
    。想想那个有计数器的模拟吧。在进行交互之前,计数器均为0。因此,在触发对mock的调用之前,向mock询问其计数器是没有意义的
  • 我不熟悉JUnit的规则;因此,我重写了测试,以便在没有它的情况下工作。如果要使用JUnit规则进行异常处理;好吧,留给读者作为练习
不相关:我发现
queryWithRetry()
声明的方法参数使用了所有大写字母,这让人感到困惑。您可以将所有UPPERCase都用于常量,但该方法参数的整体思想是:每次调用该参数时,它可能会有所不同。这只是一个正常参数。除此之外:当您使用Java8时,不要用名称来表示“thisvalueiscomininginmilyses”——使用新的Duration类来传递可以转换为毫秒的内容!但是这些变量没有在方法
queryWithRetry()
中声明?它们是大写的,因为它们是常量,在类的顶部定义。它们在整个测试文件中都被使用,但我只对代码中的第一个测试用例进行了采样。持续时间提示很有用,谢谢!
queryWithRetry()
的签名(在我看来)几乎不可读。我知道有些人喜欢把final放在任何地方,但我觉得它对方法参数没有帮助。如前所述:调用foo(BAR\u CONST)是可以的,但是方法签名不应该读为
void foo(BAR\u CONST)
——因为您不知道如何调用该方法。大写表示常量,参数不是常量。我猜这是混乱的一部分:该方法中的MAX_尝试指向该方法参数;不是那个常数。如果这两个内容都在同一个文件中,则本地MAX_trytes甚至会对外部常量进行阴影处理。而阴影是一件非常谨慎的事情!哦,我明白你的意思了。我本来不理解你的意思,因为我已经更新了本地代码以反映这一点(并且忘记编辑堆栈溢出)!我的推理方式与你的推理方式相同:)这最终对我有效,谢谢。我确实使用了
@RunWith(MockitoJUnitRunner.class)
,但忘了将其包含在我的代码示例中。还有一个问题:为什么我不能将代码示例中的
verify()
调用移到
queryWithRetry()方法下?我不明白为什么这样不行。因为那个调用抛出了一个异常,需要捕获该异常才能继续验证调用!啊,我明白了。我认为
抛出了.expect(RandomException.class)
会捕获异常,但我现在看到它只是检测异常。不客气。记录在案——我能做些什么使我的答案值得投票吗?万一你忘了,明天就可以了,因为我今天已经达到每日上限了。
private static final int MAX_TRIES = 3;
private static final int RETRY_INTERVAL_IN_MILLISECONDS = 100;

@Mock
private Callable<String> mockCallable;

@Test
public void testQueryConfigThrowsRandomExceptionOnTimeout() throws Exception {
    when(mockCallable.call()).thenThrow(new IllegalArgumentException("timeout"));
    try {
        queryWithRetry(mockCallable, MAX_TRIES, RETRY_INTERVAL_IN_MILLISECONDS);
        fail("should have thrown");
    } catch (RuntimeException re) {
        // as expected
    }
    verify(mockCallable, Mockito.times(3)).call();
}