ChatGPT解决这个技术问题 Extra ChatGPT

Can Mockito capture arguments of a method called multiple times?

I have a method that gets called twice, and I want to capture the argument of the second method call.

Here's what I've tried:

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

But I get a TooManyActualInvocations Exception, as Mockito thinks that doSomething should only be called once.

How can I verify the argument of the second call of doSomething?


C
Community

I think it should be

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

Sample from 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());

Can you capture the arguments passed to doSomething() in each separate invocation with this?
It should be noted that in case you do something like this: Person person = new Person("John"); doSomething(person); person.setName("Jane"); doSomething(person); the captured argument will be the same twice (because actually it is the same person object), so capturedPeople.get(0).getName() == capturedPeople.get(1).getName() == "Jane" , see also groups.google.com/forum/#!msg/mockito/KBRocVedYT0/5HtARMl9r2wJ.
This is nice, but how can I test two differently typed object invocations? For example ExecutorService.submit(new MyRunableImpl()); and then ExecutorService.submit(new MyAnotherRunableImpl()) ?
If one needs to handle the case described by @asmaier, I posted an answer here: stackoverflow.com/a/36574817/1466267
For anyone still wondering about the answer to Leon's question, you'd use the common base class (Runnable) and, if needed, do a more specific type check on the captured argument.
M
Maciej Dobrowolski

Since Mockito 2.0 there's also possibility to use static method Matchers.argThat(ArgumentMatcher). With the help of Java 8 it is now much cleaner and more readable to write:

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

If you're tied to lower Java version there's also not-that-bad:

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

Of course none of those can verify order of calls - for which you should use 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")));

Please take a look at mockito-java8 project which makes possible to make calls such as:

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

This is a nice technique. I'm currently getting some rather cryptic output though: "Wanted but not invoked: /n mockAppender.append( <Index manager u t$$ lambda$ 5 9/ 1 3 1 9 5 1 0 1 6> );" - the arg there is a CharSequence. Do you know of any way to get the report to print out the "wanted" arg properly?
@mikerodent The cryptic output can be fixed if you go the more verbose route of creating a class that implements ArgumentMatcher. Overriding the toString method in your implementation will provide any message you want in the mockito test output.
p
pd40

If you don't want to validate all the calls to doSomething(), only the last one, you can just use ArgumentCaptor.getValue(). According to the Mockito javadoc:

If the method was called multiple times then it returns the latest captured value

So this would work (assumes Foo has a method 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());

is there any way to capture both the values?
M
Michał Stochmal

You can also use @Captor annotated ArgumentCaptor. For example:

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

S
Suraj Rao

With Java 8's lambdas, a convenient way is to use

org.mockito.invocation.InvocationOnMock

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

I'm not able to see how this is more convenient than the old way. I love good use of lambdas, but I'm not sure if this is one.
f
fl0w

First of all: you should always import mockito static, this way the code will be much more readable (and intuitive) - the code samples below require it to work:

import static org.mockito.Mockito.*;

In the verify() method you can pass the ArgumentCaptor to assure execution in the test and the ArgumentCaptor to evaluate the arguments:

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

The list of all passed arguments during your test is accessible via the argument.getAllValues() method.

The single (last called) argument's value is accessible via the argument.getValue() for further manipulation / checking or whatever you wish to do.