简体   繁体   中英

Mockito verify not failing on Callable object

I have the following code for a method that performs an action on a Callable parameter:

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

I am using Mockito to try to validate that the line query.call() is attempted exactly MAX_TRIES number of times before throwing a RandomException . I try to do this with the following test code:

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

The Mockito code successfully tests that a RandomException is thrown by the method, but improperly says that the test passes.

This test should fail because I write that Mockito should verify that mockCallable.call() is performed at most once, but as far as I understand, it is called MAX_TRIES times (which is set to 3).

Could someone explain this behavior and give advice on how to properly test the number of times mockCallable.call() is invoked?

There are plenty of things wrong with your test. Here is something that does what you want:

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

(please note: I changed the exception types for my test; but that should be obvious).

So, things you got wrong:

  • I suggest to use @RunWith(MockitoJUnitRunner.class) , and then just go with @Mock annotations. You have the @Mock annotation plus a setup method that calls Mockito.mock() for the same field. That is redundant. Either use the annotation together with @RunWith, or call MockitoAnnotations.initMocks() in your setup method
  • verify() has to be called after you interacted with your mock object. Just think of that mock having counters. Before you interact, the counters are all 0. So there is no point asking a mock about its counters before you trigger the calls to the mock.
  • I am not familiar with JUnit rules; therefore I rewrote the test to work without it. If you want to use the JUnit rule for exception handling; well left to the reader as exercise.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM