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.