¿Puede Mockito capturar argumentos de un método llamado varias veces?

446

Tengo un método que se llama dos veces y quiero capturar el argumento de la segunda llamada al método.

Esto es lo que he intentado:

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

Pero obtengo una TooManyActualInvocationsexcepción, ya que Mockito piensa que doSomethingsolo debería llamarse una vez.

¿Cómo puedo verificar el argumento de la segunda llamada de doSomething?

Eric Wilson
fuente

Respuestas:

784

Creo que debería ser

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

Muestra de 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());
proactif
fuente
3
¿Puedes capturar los argumentos pasados doSomething()en cada invocación por separado con esto?
mate b
36
Cabe señalar que en caso de que haga algo como esto: Person person = new Person("John"); doSomething(person); person.setName("Jane"); doSomething(person);el argumento capturado será el mismo dos veces (porque en realidad es el mismo objeto de persona), por lo tanto capturedPeople.get(0).getName() == capturedPeople.get(1).getName() == "Jane", consulte también groups.google.com/forum/#!msg/mockito/ KBRocVedYT0 / 5HtARMl9r2wJ .
asmaier
2
Esto es bueno, pero ¿cómo puedo probar dos invocaciones de objetos con tipos diferentes? Por ejemplo ExecutorService.submit (new MyRunableImpl ()); y luego ExecutorService.submit (new MyAnotherRunableImpl ())?
Leon
Si uno necesita manejar el caso descrito por @asmaier, publiqué una respuesta aquí: stackoverflow.com/a/36574817/1466267
SpaceTrucker
1
Para cualquiera que todavía se pregunte sobre la respuesta a la pregunta de Leon, usaría la clase base común ( Runnable) y, si es necesario, haría una verificación de tipo más específica en el argumento capturado.
Matthew leyó el
50

Desde Mockito 2.0 también existe la posibilidad de utilizar el método estático Matchers.argThat (ArgumentMatcher) . Con la ayuda de Java 8, ahora es mucho más limpio y legible escribir:

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

Si está atado a una versión inferior de Java, también hay no tan malo:

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

Por supuesto, ninguno de ellos puede verificar el orden de las llamadas, para lo cual debe usar 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")));

Eche un vistazo al proyecto mockito-java8 que hace posible realizar llamadas como:

verify(mockBar).doSth(assertArg(arg -> assertThat(arg.getSurname()).isEqualTo("Surname")));
Maciej Dobrowolski
fuente
2
Esta es una buena técnica. Sin embargo, actualmente estoy obteniendo un resultado bastante críptico: "Se busca pero no se invoca: / n mockAppender.append (<Index manager ut $$ lambda $ 5 9/1 3 1 9 5 1 0 1 6>);" - el argumento hay un CharSequence. ¿Conoce alguna forma de hacer que el informe imprima el argumento "deseado" correctamente?
Mike roedor
@mikerodent El resultado críptico se puede corregir si sigue la ruta más detallada de crear una clase que implemente ArgumentMatcher <T>. Anular el método toString en su implementación proporcionará cualquier mensaje que desee en la salida de prueba de mockito.
Noah Solomon
25

Si no desea validar todas las llamadas doSomething(), solo la última, puede usarlas ArgumentCaptor.getValue(). Según el Mockito javadoc :

Si el método se llamó varias veces, devuelve el último valor capturado

Entonces esto funcionaría (se supone que Footiene un método 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());
lreeder
fuente
¿Hay alguna forma de capturar ambos valores?
Hars
9

También puede usar @Captor anotado ArgumentCaptor. Por ejemplo:

@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));
}
Michał Stochmal
fuente
6

Con las lambdas de Java 8, una forma conveniente es usar

org.mockito.invocation.InvocationOnMock

when(client.deleteByQuery(anyString(), anyString())).then(invocationOnMock -> {
    assertEquals("myCollection", invocationOnMock.getArgument(0));
    assertThat(invocationOnMock.getArgument(1), Matchers.startsWith("id:"));
}
Anton Seredkin
fuente
No puedo ver cómo esto es más conveniente que la forma anterior. Me encanta el buen uso de lambdas, pero no estoy seguro si este es uno.
Eric Wilson
0

En primer lugar: siempre debe importar mockito static, de esta manera el código será mucho más legible (e intuitivo); los ejemplos de código a continuación requieren que funcione:

import static org.mockito.Mockito.*;

En el método verificar () puede pasar el ArgumentCaptor para asegurar la ejecución en la prueba y el ArgumentCaptor para evaluar los argumentos:

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

Se puede acceder a la lista de todos los argumentos pasados ​​durante su prueba a través del método argumento.getAllValues ​​().

Se puede acceder al valor del argumento único (último llamado) a través del argumento.getValue () para una mayor manipulación / verificación o lo que sea que desee hacer.

fl0w
fuente