简体   繁体   中英

Best practices for verifying recursive method calls with Mockito

Consider this example class:

public class Processor {
  private final Dependency dependency;

  public Processor(Dependency dependency) {
    this.dependency = dependency;
  }

  public void processUsers(List<Integer> userIds, int attempt) {
    if (attempt == 0) {
      //log initial processing
    } else if (attempt > 0 && attempt < 5) {
      //log retry processing
    } else {
      //log processing limit exceeded
      return;
    }
    List<Integer> failedIds = new ArrayList<Integer>();
    for (Integer userId : userIds) {
      try {
        processUser(userId);
      } catch (Exception ex) {
        //logging
        failedIds.add(userId);
      }
    }
    if (failedIds.isEmpty()) {
      //log ok
    } else {
      processUsers(failedIds, attempt + 1);
    }
  }

  public void processUser(Integer userId) throws Exception{
    //dependency can throw exception
    dependency.call();
  }
}

I would like to verify that method processUsers calls itself when exception is thrown. Here is my testy test:

public class ProcessorTest {
  @Test
  public void processShouldCallItselfWithFailedSublistWhenProcessingFails(){
    Dependency dependency = mock(Dependency.class);
    when(dependency.call()).thenThrow(Exception.class);
    Processor processor = new Processor(dependency);
    processor.processUsers(Arrays.asList(new Integer[]{1,2,3}), 0);
    //need to verify processor will call #processUsers recursively
    //because the dependency thrown Exception, how?
  }
}

What are best practices for verifying that method calls itself recursively under certain circumstances?

I'm using TestNG + Mockito and this verbose language called JAVA

You can verify the number of times dependency.call() is invoked. This would tell you that retry worked - just by the number of calls:

@Test
public void processShouldCallItselfWithFailedSublistWhenProcessingFails() {
    Dependency dependency = mock(Dependency.class);
    when(dependency.call()).thenReturn(1, 2).thenThrow(Exception.class).
        thenReturn(4);
    Processor processor = new Processor(dependency);
    processor.processUsers(Arrays.asList(new Integer[]{1, 2, 3}), 0);
    verify(dependency, times(4)).call();
}

This would tell you that retry eventually failed with too many exceptions:

@Test
public void processShouldFailAfterTooManyRetries() {
    Dependency dependency = mock(Dependency.class);
    when(dependency.call()).thenThrow(Exception.class);
    Processor processor = new Processor(dependency);
    final List<Integer> userIds = Arrays.asList(new Integer[]{1, 2, 3});
    final int expectedRetries = 5;
    processor.processUsers(userIds, 0);
    verify(dependency, times(expectedRetries * userIds.size())).call();
}

If the call to dependency actually used the userId, then you could actually check that all the expected calls to dependency.call(int userId) happened. This would tell you that it all of them went through given enough retries:

@Test
public void processShouldCallItselfWithFailedSublistWhenProcessingFails() {
    Dependency dependency = mock(Dependency.class);
    when(dependency.call(anyInt())).
        thenReturn(1, 2).thenThrow(Exception.class).thenReturn(4);
    Processor processor = new Processor(dependency);
    processor.processUsers(Arrays.asList(new Integer[]{1, 2, 3}), 0);
    verify(dependency).call(1);
    verify(dependency).call(2);
    verify(dependency, times(2)).call(3);
}

@Test
public void processShouldFailAfterTooManyRetries() {
    Dependency dependency = mock(Dependency.class);
    when(dependency.call(anyInt())).thenThrow(Exception.class);
    Processor processor = new Processor(dependency);
    final List<Integer> userIds = Arrays.asList(new Integer[]{1, 2, 3});
    final int expectedRetries = 5;
    processor.processUsers(userIds, 0);
    for (Integer userId : userIds) {
        verify(dependency, times(expectedRetries)).call(userId);
    }
}

Not sure about best practices but you can achieve same by verifying number of times the method is invoked

 int invocationCount = 5; // Or any other desired number
 verify(processor,times(invocationCount)).processUsers();

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