使用 Mockito 验证递归方法调用的最佳实践

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

考虑这个示例类:

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

我想验证方法

processUsers
在引发异常时调用自身。 这是我的测试:

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?
  }
}

验证该方法在某些情况下递归调用自身的最佳实践是什么?

我正在使用 TestNG + Mockito,这种冗长的语言称为

JAVA

java unit-testing mocking mockito
3个回答
3
投票

您可以验证调用

dependency.call()
的次数。这会告诉您重试有效 - 仅通过调用次数即可:

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

这会告诉您重试最终因太多异常而失败:

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

如果对依赖项的调用实际上使用了 userId,那么您实际上可以检查是否发生了对

dependency.call(int userId)
的所有预期调用。这会告诉您,所有这些都经过了足够的重试:

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

1
投票

不确定最佳实践,但您可以通过验证调用方法的次数来实现相同的目的

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

0
投票

我遇到了同样的问题,但没有找到现成的解决方案,所以这里有一个对我有用的东西:监视参数对象。

创建一个参数对象并修改它,以便您的方法按如下方式使用它:

public class Processor {
    private final Dependency dependency;

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

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

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

    protected static class ProcessorParameters
    {
        private List<Integer> userIds;
        private int attempt = 0;

        public ProcessorParameters userIds(List<Integer> userIds) {
            this.userIds = userIds;
            return this;
        }

        public ProcessorParameters next(int attempt) {
            this.attempt = attempt;
            return this;
        }
    }
}

这是你的测试类的样子:

class ProcessorTest
{
    @Test
    public void processShouldCallItselfWithFailedSublistWhenProcessingFails() throws Exception
    {
        Dependency dependency = mock(Dependency.class);
        doThrow(new Exception()).when(dependency).call();
        Processor processor = new Processor(dependency);

        // Allows you to see what parameters are passed, but this object
        // still serves as the method parameters.
        ProcessorParameters params = spy(new ProcessorParameters());

        processor.processUsers(params.userIds(Arrays.asList(new Integer[]{1,2,3})));

        ArgumentCaptor<Integer> attemptCaptor = ArgumentCaptor.forClass(Integer.class);
        ArgumentCaptor<List<Integer>> userIdsCaptor = ArgumentCaptor.forClass(List.class);
        verify(params, times(6)).userIds(userIdsCaptor.capture());
        verify(params, times(5)).next(attemptCaptor.capture());

        // Each entry in here is another recursive call to processUsers
        // You can capture the value of each parameter passed in order.
        userIdsCaptor.getAllValues().forEach(System.out::println);
        attemptCaptor.getAllValues().forEach(System.out::println);

        verify(dependency, times(15)).call();
    }
}

虽然这不允许您直接验证“processUsers”是否是在传统 Mockito 意义上调用的,但它确实增加了很多洞察力。您可以捕获参数来观察递归行为,可以向前/向后跳跃,可以查看某些用户 ID 的调用顺序等。也就是说,您可以仅针对某些用户抛出异常并验证后续行为.

© www.soinside.com 2019 - 2024. All rights reserved.