【问题标题】:Can Mockito capture arguments of a method called multiple times?Mockito 可以捕获多次调用的方法的参数吗?
【发布时间】:2011-08-24 07:23:13
【问题描述】:

我有一个被调用两次的方法,我想捕获第二个方法调用的参数。

这是我尝试过的:

ArgumentCaptor<Foo> firstFooCaptor = ArgumentCaptor.forClass(Foo.class);
ArgumentCaptor<Foo> secondFooCaptor = ArgumentCaptor.forClass(Foo.class);
verify(mockBar).doSomething(firstFooCaptor.capture());
verify(mockBar).doSomething(secondFooCaptor.capture());
// then do some assertions on secondFooCaptor.getValue()

但是我得到了一个 TooManyActualInvocations 异常,因为 Mockito 认为 doSomething 应该只被调用一次。

如何验证doSomething第二次调用的参数?

【问题讨论】:

    标签: java unit-testing mocking mockito


    【解决方案1】:

    我觉得应该是的

    verify(mockBar, times(2)).doSomething(...)
    

    来自mockito javadoc的示例:

    ArgumentCaptor<Person> peopleCaptor = ArgumentCaptor.forClass(Person.class);
    verify(mock, times(2)).doSomething(peopleCaptor.capture());
    
    List<Person> capturedPeople = peopleCaptor.getAllValues();
    assertEquals("John", capturedPeople.get(0).getName());
    assertEquals("Jane", capturedPeople.get(1).getName());
    

    【讨论】:

    • 你能在每个单独的调用中捕获传递给doSomething()的参数吗?
    • 应该注意,如果你这样做:Person person = new Person("John"); doSomething(person); person.setName("Jane"); doSomething(person); 捕获的参数将是相同的两次(因为实际上它是同一个人对象),所以 capturedPeople.get(0).getName() == capturedPeople.get(1).getName() == "Jane" ,另见groups.google.com/forum/#!msg/mockito/KBRocVedYT0/5HtARMl9r2wJ.
    • 这很好,但是如何测试两个不同类型的对象调用呢?例如 ExecutorService.submit(new MyRunableImpl());然后是 ExecutorService.submit(new MyAnotherRunableImpl()) ?
    • 如果需要处理@asmaier 描述的情况,我在这里发布了一个答案:stackoverflow.com/a/36574817/1466267
    • 对于仍然想知道 Leon 问题的答案的任何人,您可以使用公共基类 (Runnable),如果需要,对捕获的参数进行更具体的类型检查。跨度>
    【解决方案2】:

    从 Mockito 2.0 开始,也可以使用静态方法 Matchers.argThat(ArgumentMatcher)。在 Java 8 的帮助下,它现在更简洁、更易读:

    verify(mockBar).doSth(argThat((arg) -> arg.getSurname().equals("OneSurname")));
    verify(mockBar).doSth(argThat((arg) -> arg.getSurname().equals("AnotherSurname")));
    

    如果您使用较低的 Java 版本,那么也没有那么糟糕:

    verify(mockBar).doSth(argThat(new ArgumentMatcher<Employee>() {
            @Override
            public boolean matches(Object emp) {
                return ((Employee) emp).getSurname().equals("SomeSurname");
            }
        }));
    

    当然,这些都不能验证调用顺序——你应该使用InOrder

    InOrder inOrder = inOrder(mockBar);
    
    inOrder.verify(mockBar).doSth(argThat((arg) -> arg.getSurname().equals("FirstSurname")));
    inOrder.verify(mockBar).doSth(argThat((arg) -> arg.getSurname().equals("SecondSurname")));
    

    请查看mockito-java8 项目,该项目可以进行如下调用:

    verify(mockBar).doSth(assertArg(arg -> assertThat(arg.getSurname()).isEqualTo("Surname")));
    

    【讨论】:

    • 这是一个不错的技术。我目前得到一些相当神秘的输出:“想要但未调用:/n mockAppender.append();” - arg 有一个CharSequence。你知道有什么方法可以让报告正确打印出“想要的”arg吗?
    • @mikerodent 如果您采用更详细的方法来创建实现 ArgumentMatcher 的类,则可以修复神秘的输出。在您的实现中覆盖 toString 方法将在 mockito 测试输出中提供您想要的任何消息。
    【解决方案3】:

    如果您不想验证对doSomething() 的所有调用,只验证最后一个,您可以使用ArgumentCaptor.getValue()。根据Mockito javadoc

    如果该方法被多次调用,则返回最新捕获的值

    所以这会起作用(假设Foo 有一个方法getName()):

    ArgumentCaptor<Foo> fooCaptor = ArgumentCaptor.forClass(Foo.class);
    verify(mockBar, times(2)).doSomething(fooCaptor.capture());
    //getValue() contains value set in second call to doSomething()
    assertEquals("2nd one", fooCaptor.getValue().getName());
    

    【讨论】:

    • 有什么方法可以同时捕获这两个值吗?
    【解决方案4】:

    您也可以使用 @Captor 注释的 ArgumentCaptor。例如:

    @Mock
    List<String> mockedList;
    
    @Captor
    ArgumentCaptor<String> argCaptor;
    
    @BeforeTest
    public void init() {
        //Initialize objects annotated with @Mock, @Captor and @Spy.
        MockitoAnnotations.initMocks(this);
    }
    
    @Test
    public void shouldCallAddMethodTwice() {
        mockedList.add("one");
        mockedList.add("two");
        Mockito.verify(mockedList, times(2)).add(argCaptor.capture());
    
        assertEquals("one", argCaptor.getAllValues().get(0));
        assertEquals("two", argCaptor.getAllValues().get(1));
    }
    

    【讨论】:

      【解决方案5】:

      使用 Java 8 的 lambda,一种方便的方法是使用

      org.mockito.invocation.InvocationOnMock
      
      when(client.deleteByQuery(anyString(), anyString())).then(invocationOnMock -> {
          assertEquals("myCollection", invocationOnMock.getArgument(0));
          assertThat(invocationOnMock.getArgument(1), Matchers.startsWith("id:"));
      }
      

      【讨论】:

      • 我看不出这比旧方法更方便。我喜欢很好地使用 lambda,但我不确定这是不是一个。
      【解决方案6】:

      首先:您应该始终导入 mockito static,这样代码将更具可读性(和直观性) - 下面的代码示例需要它工作:

      import static org.mockito.Mockito.*;
      

      在 verify() 方法中,您可以传递 ArgumentCaptor 以确保在测试中执行,并传递 ArgumentCaptor 以评估参数:

      ArgumentCaptor<MyExampleClass> argument = ArgumentCaptor.forClass(MyExampleClass.class);
      verify(yourmock, atleast(2)).myMethod(argument.capture());
      
      List<MyExampleClass> passedArguments = argument.getAllValues();
      
      for (MyExampleClass data : passedArguments){
          //assertSometing ...
          System.out.println(data.getFoo());
      }
      

      可以通过 argument.getAllValues() 方法访问测试期间所有传递参数的列表。

      可以通过 argument.getValue() 访问单个(最后调用的)参数的值,以进行进一步的操作/检查或您想做的任何事情。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-02-27
        • 2012-02-23
        • 1970-01-01
        • 2011-12-26
        相关资源
        最近更新 更多