Mockito può catturare argomenti di un metodo chiamato più volte?


446

Ho un metodo che viene chiamato due volte e voglio catturare l'argomento della seconda chiamata al metodo.

Ecco cosa ho provato:

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

Ma ricevo TooManyActualInvocationsun'eccezione, poiché Mockito pensa che doSomethingdovrebbe essere chiamato solo una volta.

Come posso verificare l'argomento della seconda chiamata di doSomething?

Risposte:


784

Penso che dovrebbe essere

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

Campione dal 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());

3
Riesci a catturare gli argomenti passati doSomething()in ogni invocazione separata con questo?
matt b,

36
Va notato che nel caso in cui tu faccia qualcosa del genere: Person person = new Person("John"); doSomething(person); person.setName("Jane"); doSomething(person);l'argomento catturato sarà lo stesso due volte (perché in realtà è lo stesso oggetto persona), quindi capturedPeople.get(0).getName() == capturedPeople.get(1).getName() == "Jane", vedi anche groups.google.com/forum/#!msg/mockito/ KBRocVedYT0 / 5HtARMl9r2wJ .
asmaier,

2
Questo è carino, ma come posso testare due invocazioni di oggetti di tipo diverso? Ad esempio ExecutorService.submit (new MyRunableImpl ()); e poi ExecutorService.submit (new MyAnotherRunableImpl ())?
Leon,

Se si ha bisogno per gestire il caso descritto da @asmaier, ho postato una risposta qui: stackoverflow.com/a/36574817/1466267
SpaceTrucker

1
Per chiunque si stia ancora chiedendo quale sia la risposta alla domanda di Leon, useresti la classe base comune ( Runnable) e, se necessario, eseguiresti un controllo del tipo più specifico sull'argomento catturato.
Matteo Leggi il

50

Da Mockito 2.0 esiste anche la possibilità di utilizzare il metodo statico Matchers.argThat (ArgumentMatcher) . Con l'aiuto di Java 8 è ora molto più pulito e leggibile scrivere:

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

Se sei legato a una versione Java inferiore, c'è anche qualcosa di non male:

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

Naturalmente nessuno di questi può verificare l'ordine delle chiamate, per il quale è necessario utilizzare 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")));

Dai un'occhiata al progetto mockito-java8 che rende possibile effettuare chiamate come:

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

2
Questa è una bella tecnica. Attualmente sto ottenendo un output piuttosto criptico però: "Ricercato ma non invocato: / n mockAppender.append (<Gestore indice ut $$ lambda $ 5 9/1 3 1 9 5 1 0 1 6>);" - l'arg c'è un CharSequence. Conosci un modo per ottenere il rapporto per stampare correttamente l'arg "desiderato"?
Mike Rodent,

@mikerodent L'output criptico può essere risolto se segui la strada più dettagliata per creare una classe che implementa ArgumentMatcher <T>. La sostituzione del metodo toString nella tua implementazione fornirà qualsiasi messaggio desiderato nell'output del test mockito.
Noah Solomon,

25

Se non vuoi convalidare tutte le chiamate doSomething(), solo l'ultima, puoi semplicemente usare ArgumentCaptor.getValue(). Secondo il Mockito javadoc :

Se il metodo è stato chiamato più volte, restituisce l'ultimo valore acquisito

Quindi funzionerebbe (suppone Fooabbia un metodo 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());

c'è un modo per catturare entrambi i valori?
Hars l'

9

Puoi anche usare ArgumentCaptor con annotazioni @Captor. Per esempio:

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

6

Con le lambda di Java 8, un modo conveniente è usare

org.mockito.invocation.InvocationOnMock

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

Non riesco a vedere come questo sia più conveniente del vecchio modo. Adoro il buon uso degli lambda, ma non sono sicuro che sia uno di questi.
Eric Wilson,

0

Prima di tutto: dovresti sempre importare mockito static, in questo modo il codice sarà molto più leggibile (e intuitivo) - i seguenti esempi di codice richiedono che funzioni:

import static org.mockito.Mockito.*;

Nel metodoify () è possibile passare ArgumentCaptor per assicurare l'esecuzione nel test e ArgumentCaptor per valutare gli argomenti:

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

L'elenco di tutti gli argomenti passati durante il test è accessibile tramite il metodo argomento.getAllValues ​​().

Il valore dell'argomento singolo (ultimo chiamato) è accessibile tramite argomento.getValue () per ulteriori manipolazioni / controlli o qualunque cosa tu voglia fare.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.