【问题标题】:Mockito verify not failing on Callable objectMockito 验证在 Callable 对象上没有失败
【发布时间】:2017-07-17 22:12:16
【问题描述】:

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

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 尝试验证 query.call() 行在抛出 RandomException 之前被尝试了 MAX_TRIES 的次数。我尝试使用以下测试代码来做到这一点:

public class CallableQueryTest {
  private static final int MAX_TRIES = 3;
  private static final int RETRY_INTERVAL_IN_MILLISECONDS = 100;

  @Mock
  private Callable<Document> mockCallable;

  @Rule
  public ExpectedException thrown = ExpectedException.none();

  @Before
  public void setUp() throws Exception {
    mockCallable = Mockito.mock(Callable.class);
  }

  @Test
  public void testQueryConfigThrowsRandomExceptionOnTimeout() throws Exception {
    Mockito.when(mockCallable.call()).thenThrow(new TimeoutException("timeout"));

    thrown.expect(RandomException.class);    
    Mockito.verify(mockCallable, Mockito.atMost(1)).call();
    MongoQueryUtils.queryWithRetry(mockCallable, MAX_TRIES, RETRY_INTERVAL_IN_MILLISECONDS);
  }
}

Mockito 代码成功测试了该方法抛出了 RandomException,但错误地表示测试通过了。

这个测试应该失败,因为我写道 Mockito 应该验证 mockCallable.call() 最多执行一次,但据我了解,它被称为 MAX_TRIES 次(设置为 3)。

有人可以解释这种行为并就如何正确测试mockCallable.call() 被调用的次数提供建议吗?

【问题讨论】:

  • 不相关:我发现您对 queryWithRetry() 声明的 方法参数 使用全部大写的事实令人困惑。您对 CONSTANTS 使用全部大写 - 但赋予该方法的参数的整个想法是:它可能在每次被调用时都不同。这只是一个正常的参数。除此之外:当您使用 Java8 时,只需 not 通过它的 name 说“这个值以毫秒为单位” - 使用新的 Duration 类来简单地传递可以变成毫秒!
  • 但是这些变量没有在方法queryWithRetry()中声明?它们是大写的,因为它们是常量,定义在类的顶部。它们在整个测试文件中都使用过,但我只对代码中的第一个测试用例进行了采样。持续时间提示很有帮助,谢谢!
  • queryWithRetry() 的签名(在我看来)几乎无法阅读。我知道有些人喜欢把 final 放在任何地方,但我发现它对方法参数没有帮助。如前所述:调用 foo(BAR_CONST) 很好,但方法签名应该读取void foo(Bar BAR_CONST) - 因为您不知道如何调用该方法。大写用于常量,参数不是常量。所有恕我直言。
  • 我猜这是混淆的一部分:MAX_TRIES within 该方法指向该方法参数; 不是那个常数。如果这两个东西都在同一个文件中,则本地 MAX_TRIES 甚至会 shadow 外部常量。阴影是非常谨慎的事情!
  • 哦,我明白你的意思了。我最初并不了解您,因为我已经更新了本地代码以反映这一点(并且忘记编辑堆栈溢出)!我的推理方式和你一样:)

标签: java unit-testing mocking mockito callable


【解决方案1】:

您的测试有很多问题。这是你想做的事情:

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();
}

(请注意:我更改了测试的异常类型;但这应该很明显)。

所以,你错了:

  • 我建议使用@RunWith(MockitoJUnitRunner.class),然后使用@Mock 注释。您有@Mock 注释加上 一个设置方法,该方法为同一字段调用 Mockito.mock()。那是多余的。将注解与@RunWith 一起使用,或在您的设置方法中调用 MockitoAnnotations.initMocks()
  • verify() 必须在您与模拟对象交互之后调用。想想那个有计数器的模拟。在您进行交互之前,计数器都是 0。因此,在您触发对模拟的调用之前询问模拟的计数器是没有意义的。
  • 我不熟悉 JUnit 规则;因此我重写了测试以在没有它的情况下工作。如果要使用 JUnit 规则进行异常处理;留给读者作为练习。

【讨论】:

  • 这最终对我有用,谢谢。我确实使用了@RunWith(MockitoJUnitRunner.class),但忘记将它包含在我的代码示例中。还有一个问题:为什么我不能将代码示例中的verify() 调用移到queryWithRetry() 方法下方?我不明白为什么那行不通。
  • 因为该调用引发了一个异常,需要捕获该异常才能继续进行验证调用!
  • 啊,我明白了。我以为thrown.expect(RandomException.class); 会捕捉到异常,但我现在看到它只是检测到异常。
  • 不客气。作为记录 - 我可以做些什么来让我的回答值得投票吗?万一您忘记了 - 明天可以随意这样做,因为我今天已经达到每日上限。
猜你喜欢
  • 2015-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多