Czy Mockito może przechwytywać argumenty metody wywoływanej wielokrotnie?

446

Mam metodę, która jest wywoływana dwukrotnie i chcę uchwycić argument drugiego wywołania metody.

Oto, co próbowałem:

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

Ale dostaję TooManyActualInvocationswyjątek, ponieważ Mockito uważa, że doSomethingnależy to nazwać tylko raz.

Jak mogę zweryfikować argument drugiego wywołania doSomething?

Eric Wilson
źródło

Odpowiedzi:

784

Myślę, że tak powinno być

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

Przykład z 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
źródło
3
Czy możesz w ten sposób uchwycić argumenty przekazane doSomething()w każdym osobnym wywołaniu?
matt b
36
Należy zauważyć, że w przypadku zrobienia czegoś takiego: Person person = new Person("John"); doSomething(person); person.setName("Jane"); doSomething(person);przechwycony argument będzie dwa razy taki sam (ponieważ w rzeczywistości jest to obiekt tej samej osoby), więc capturedPeople.get(0).getName() == capturedPeople.get(1).getName() == "Jane"zobacz także groups.google.com/forum/#!msg/mockito/ KBRocVedYT0 / 5HtARMl9r2wJ .
asmaier
2
To miłe, ale jak mogę przetestować dwie różne typy wywołań obiektów? Na przykład ExecutorService.submit (new MyRunableImpl ()); a następnie ExecutorService.submit (new MyAnotherRunableImpl ())?
Leon,
Jeśli trzeba poradzić sobie ze sprawą opisaną przez @asmaier, opublikowałem odpowiedź tutaj: stackoverflow.com/a/36574817/1466267
SpaceTrucker
1
Dla każdego, kto wciąż zastanawia się nad odpowiedzią na pytanie Leona, użyłbyś wspólnej klasy bazowej ( Runnable) i, jeśli to konieczne, zrobiłbyś bardziej szczegółową kontrolę typu na przechwyconym argumencie.
Mateusz przeczytał
50

Od wersji Mockito 2.0 istnieje również możliwość użycia metody statycznej Matchers.argThat (ArgumentMatcher) . Przy pomocy Java 8 pisanie jest teraz znacznie łatwiejsze i bardziej czytelne:

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

Jeśli jesteś przywiązany do niższej wersji Java, jest też niezły:

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

Oczywiście żaden z nich nie może zweryfikować kolejności połączeń - do których należy użyć 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")));

Proszę spojrzeć na projekt mockito-java8 , który umożliwia wykonywanie połączeń takich jak:

verify(mockBar).doSth(assertArg(arg -> assertThat(arg.getSurname()).isEqualTo("Surname")));
Maciej Dobrowolski
źródło
2
To niezła technika. Obecnie dostaję raczej dość tajemnicze wyjście: „Chciałem, ale nie został wywołany: / n mockAppender.append (<Menedżer indeksu ut $$ lambda 5 9/1 3 1 9 5 1 0 1 6>);” - Arg jest tam CharSequence. Czy znasz jakiś sposób na prawidłowe wydrukowanie raportu „poszukiwany” argument?
Mike Gryzonie
@mikerodent Zaszyfrowane dane wyjściowe można naprawić, jeśli wybierzesz bardziej szczegółową ścieżkę tworzenia klasy, która implementuje ArgumentMatcher <T>. Przesłonięcie metody toString w implementacji spowoduje wyświetlenie dowolnego komunikatu w wynikach testu mockito.
Noah Solomon
25

Jeśli nie chcesz sprawdzać poprawności wszystkich połączeń doSomething(), możesz użyć tylko ostatniego ArgumentCaptor.getValue(). Według Mockito javadoc :

Jeśli metoda została wywołana wiele razy, wówczas zwraca ostatnią zarejestrowaną wartość

To by zadziałało (zakładając, że Fooma metodę 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());
leder
źródło
czy jest jakiś sposób na uchwycenie obu wartości?
Hars,
9

Możesz także użyć @Captor z komentarzem ArgumentCaptor. Na przykład:

@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
źródło
6

W lambdach Java 8 wygodnym sposobem jest użycie

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
źródło
Nie jestem w stanie zobaczyć, jak to jest wygodniejsze niż po staremu. Uwielbiam dobre wykorzystanie jagniąt, ale nie jestem pewien, czy to jest jeden.
Eric Wilson,
0

Po pierwsze: zawsze powinieneś importować mockito static, w ten sposób kod będzie znacznie bardziej czytelny (i intuicyjny) - poniższe przykłady kodu wymagają jego działania:

import static org.mockito.Mockito.*;

W metodzie Verse () można przekazać ArgumentCaptor, aby zapewnić wykonanie testu, oraz ArgumentCaptor, aby ocenić argumenty:

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

Lista wszystkich przekazanych argumentów podczas testu jest dostępna za pomocą metody argument.getAllValues ​​().

Wartość pojedynczego (ostatnio wywoływanego) argumentu jest dostępna poprzez argument.getValue () w celu dalszej manipulacji / sprawdzania lub cokolwiek innego, co chcesz zrobić.

fl0w
źródło