Kann Mockito Argumente einer mehrfach aufgerufenen Methode erfassen?


446

Ich habe eine Methode, die zweimal aufgerufen wird, und ich möchte das Argument des zweiten Methodenaufrufs erfassen.

Folgendes habe ich versucht:

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

Aber ich bekomme eine TooManyActualInvocationsAusnahme, da Mockito meint, dass doSomethingdas nur einmal aufgerufen werden sollte.

Wie kann ich das Argument des zweiten Aufrufs von überprüfen doSomething?

Antworten:


782

Ich denke es sollte sein

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

Probe aus 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
Können Sie damit die Argumente erfassen, die doSomething()in jedem einzelnen Aufruf übergeben wurden?
Matt B

36
Beachten Sie, dass für den Fall, dass Sie Folgendes tun: Person person = new Person("John"); doSomething(person); person.setName("Jane"); doSomething(person);Das erfasste Argument zweimal dasselbe ist (da es sich tatsächlich um dasselbe Personenobjekt handelt) capturedPeople.get(0).getName() == capturedPeople.get(1).getName() == "Jane". Siehe auch groups.google.com/forum/#!msg/mockito/ KBRocVedYT0 / 5HtARMl9r2wJ .
Asmaier

2
Das ist schön, aber wie kann ich zwei unterschiedlich typisierte Objektaufrufe testen? Zum Beispiel ExecutorService.submit (neues MyRunableImpl ()); und dann ExecutorService.submit (neues MyAnotherRunableImpl ())?
Leon

Wenn man den von @asmaier beschriebenen Fall behandeln muss, habe ich hier eine Antwort gepostet: stackoverflow.com/a/36574817/1466267
SpaceTrucker

1
Für alle, die sich noch über die Antwort auf Leons Frage wundern, würden Sie die gemeinsame Basisklasse ( Runnable) verwenden und bei Bedarf eine spezifischere Typprüfung für das erfasste Argument durchführen.
Matthew Read

50

Seit Mockito 2.0 besteht auch die Möglichkeit, die statische Methode Matchers.argThat (ArgumentMatcher) zu verwenden . Mit Hilfe von Java 8 ist es jetzt viel sauberer und lesbarer zu schreiben:

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

Wenn Sie an eine niedrigere Java-Version gebunden sind, gibt es auch nichts Schlimmes:

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

Natürlich kann keiner von diesen die Reihenfolge der Anrufe überprüfen - für die Sie InOrder verwenden sollten :

InOrder inOrder = inOrder(mockBar);

inOrder.verify(mockBar).doSth(argThat((arg) -> arg.getSurname().equals("FirstSurname")));
inOrder.verify(mockBar).doSth(argThat((arg) -> arg.getSurname().equals("SecondSurname")));

Bitte werfen Sie einen Blick auf das mockito-java8- Projekt, mit dem Sie Anrufe tätigen können wie:

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

2
Das ist eine schöne Technik. Ich erhalte derzeit jedoch eine ziemlich kryptische Ausgabe: "Gesucht, aber nicht aufgerufen: / n mockAppender.append (<Indexmanager ut $$ lambda $ 5 9/1 3 1 9 5 1 0 1 6>);" - das Argument gibt es ein CharSequence. Kennen Sie eine Möglichkeit, den Bericht dazu zu bringen, das "gesuchte" Argument richtig auszudrucken?
Mike Nagetier

@mikerodent Die kryptische Ausgabe kann behoben werden, wenn Sie eine Klasse ausführlicher erstellen, die ArgumentMatcher <T> implementiert. Wenn Sie die toString-Methode in Ihrer Implementierung überschreiben, wird jede gewünschte Meldung in der Mockito-Testausgabe angezeigt.
Noah Solomon

25

Wenn Sie nicht alle Anrufe überprüfen möchten doSomething(), können Sie nur den letzten verwenden ArgumentCaptor.getValue(). Laut dem Mockito Javadoc :

Wenn die Methode mehrmals aufgerufen wurde, wird der zuletzt erfasste Wert zurückgegeben

Das würde also funktionieren (setzt Fooeine Methode voraus 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());

Gibt es eine Möglichkeit, beide Werte zu erfassen?
Hars

9

Sie können auch @Captor annotated ArgumentCaptor verwenden. Zum Beispiel:

@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

Mit den Lambdas von Java 8 ist die Verwendung bequem

org.mockito.invocation.InvocationOnMock

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

Ich kann nicht sehen, wie bequemer das ist als der alte Weg. Ich liebe die gute Verwendung von Lambdas, aber ich bin mir nicht sicher, ob dies einer ist.
Eric Wilson

0

Zuallererst: Sie sollten immer mockito static importieren, damit der Code viel besser lesbar (und intuitiv) ist - die folgenden Codebeispiele erfordern, dass er funktioniert:

import static org.mockito.Mockito.*;

In der verify () -Methode können Sie den ArgumentCaptor übergeben, um die Ausführung im Test sicherzustellen, und den ArgumentCaptor, um die Argumente auszuwerten:

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

Auf die Liste aller während Ihres Tests übergebenen Argumente kann über die argument.getAllValues ​​() -Methode zugegriffen werden.

Auf den Wert des einzelnen (zuletzt aufgerufenen) Arguments kann über argument.getValue () zugegriffen werden, um weitere Manipulationen / Überprüfungen vorzunehmen oder was auch immer Sie tun möchten.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.