it-swarm.com.de

mockito-Verifizierung der Interaktionen mit ArgumentCaptor

Um die Anzahl der Interaktionen mit einem Modell zu überprüfen, bei dem der Parameter im Methodenaufruf einen bestimmten Typ hat, kann man dies tun

mock.someMethod(new FirstClass());
mock.someMethod(new OtherClass());
verify(mock, times(1)).someMethod(isA(FirstClass.class));

Dies wird dank des Aufrufs von isA weitergegeben, da someMethod zweimal, jedoch nur einmal mit dem Argument FirstClass aufgerufen wurde

Dieses Muster scheint jedoch nicht möglich zu sein, wenn ein ArgumentCaptor verwendet wird, auch wenn der Captor für das bestimmte Argument erstellt wurde. FirstClass

das geht nicht

mock.someMethod(new FirstClass());
mock.someMethod(new OtherClass());
ArgumentCaptor<FirstClass> captor = ArgumentCaptor.forClass(FirstClass.class);
verify(mock, times(1)).someMethod(captor.capture());

es heißt, der Schein wurde mehr als einmal gerufen.

Gibt es eine Möglichkeit, diese Überprüfung durchzuführen, während das Argument zur weiteren Überprüfung erfasst wird?

22
Hilikus

Ich empfehle die Hamcrest-Integration von Mockito, um einen guten, sauberen Matcher dafür zu schreiben. So können Sie die Überprüfung mit einer detaillierten Prüfung des übergebenen Arguments kombinieren:

verify(mock, times(1)).someMethod(argThat(personNamed("Bob")));

Matcher<Person> personNamed(final String name) {
    return new TypeSafeMatcher<Person>() {
        public boolean matchesSafely(Person item) {
            return name.equals(item.getName());
        }
        public void describeTo(Description description) {
            description.appendText("a Person named " + name);
        }
    };
}

Matcher führen im Allgemeinen zu besser lesbaren Tests und nützlicheren Testfehlernachrichten. Sie sind in der Regel auch sehr wiederverwendbar und Sie werden eine Bibliothek erstellen, in der Sie Ihr Projekt testen können. Schließlich können Sie sie auch für normale Testassertionen mit JUnit's Assert.assertThat() verwenden, sodass Sie sie doppelt nutzen können.

26
Ryan Stewart

Zitieren der Dokumente:

Beachten Sie, dass eine ArgumentCaptor keine Typprüfungen durchführt , es handelt sich nur um um das Casting im Code zu vermeiden. Dies kann sich jedoch in einer zukünftigen Hauptversion ändern (Typ Überprüfungen könnten hinzugefügt werden).

Ich würde keine ArgumentCaptor dafür verwenden. Diese Klasse erfasst (wörtlich) alles, unabhängig davon, welche Klasse als .forClass-Argument angegeben wurde. 

Um das zu erreichen, was Sie wollen, schlage ich vor, das Argument mit der Answer-Schnittstelle von Mockito abzufangen:

private FirstClass lastArgument;

@Test
public void captureFirstClass() throws Exception {
    doAnswer(captureLastArgument()).when(mock).someMethod(anInstanceOfFirstClass());
    mock.someMethod(new FirstClass());
    mock.someMethod(new OtherClass());

    verify(mock, times(1)).someMethod(anInstanceOfFirstClass());
    //write your desired matchers against lastArgument object
}

private Answer<FirstClass> captureLastArgument() {
    return new Answer<FirstClass>() {
        @Override
        public FirstClass answer(InvocationOnMock invocation) throws Throwable {
            TestClass.this.lastArgument = (FirstClass) invocation.getArguments()[0];
            return null;
        }
    };
}

private static Object anInstanceOfFirstClass(){
    return Mockito.argThat(isA(FirstClass.class));
}
6

Sie können den Captor zum Erfassen verwenden und dann die Anzahl der Aufrufe mit jedem Argumenttyp separat überprüfen. 

    // given
    ArgumentCaptor<AA> captor = ArgumentCaptor.forClass(AA.class);
    CC cc = new CC();
    // when
    cut.someMethod(new AA());
    cut.someMethod(new BB());
    cut.someMethod(new BB());
    cut.someMethod(cc);
    // then
    Mockito.verify(collaborator, atLeastOnce()).someMethod(captor.capture());
    Mockito.verify(collaborator, times(1)).someMethod(isA(AA.class));
    Mockito.verify(collaborator, times(2)).someMethod(isA(BB.class));
    Mockito.verify(collaborator, times(1)).someMethod(isA(CC.class));
    assertEquals(cc, captor.getValue());

Anscheinend wirkt sich der generische Typ der Captor-Referenz zur Laufzeit nicht aus.

4
Keith

Ich bin heute auch auf dieses Problem gestoßen. Ich dachte, ich könnte einfach so etwas machen

verify(mock).someMethod(and(isA(FirstClass.class), captor.capture()));

aber ich konnte es nicht zum Laufen bringen. Ich endete mit dieser Lösung:

@Test
public void Test() throws Exception {
    final ArgumentCaptor<FirstClass> captor = ArgumentCaptor.forClass(FirstClass.class);

    mock.someMethod(new FirstClass());
    mock.someMethod(new OtherClass());

    verify(eventBus, atLeastOnce()).post(captor.capture());
    final List<FirstClass> capturedValues = typeCheckedValues(captor.getAllValues(), FirstClass.class);
    assertThat(capturedValues.size(), is(1));
    final FirstClass capturedValue = capturedValues.get(0);
    // Do assertions on capturedValue
}

private static <T> List<T> typeCheckedValues(List<T> values, Class<T> clazz) {
    final List<T> typeCheckedValues = new ArrayList<>();
    for (final T value : values) {
        if (clazz.isInstance(value)) {
            typeCheckedValues.add(value);
        }
    }
    return typeCheckedValues;
}

Hinweis: Wenn nur eine Klasse auf diese Weise erfasst werden muss, kann typeCheckedValues folgendermaßen vereinfacht werden:

private static List<FirstClass> typeCheckedValues(List<FirstClass> values) {
    final List<FirstClass> typeCheckedValues = new ArrayList<>();
    for (final Object value : values) {
        if (value instanceof FirstClass) {
            typeCheckedValues.add((FirstClass) value);
        }
    }
    return typeCheckedValues;
}
0
LoPoBo