web-dev-qa-db-fra.com

mockito vérifie les interactions avec ArgumentCaptor

Pour vérifier le nombre d'interactions avec une maquette lorsque le paramètre dans l'appel de méthode est d'un certain type, on peut faire

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

Cela passera grâce à l'appel à isA puisque someMethod a été appelé deux fois mais seulement une fois avec l'argument FirstClass

Cependant, ce modèle semble ne pas être possible lorsqu’on utilise un ArgumentCaptor, même si le capteur a été créé pour l’argument particulier FirstClass

ça ne marche pas

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

il dit que la maquette a été appelée plus d'une fois.

Y a-t-il un moyen d'accomplir cette vérification tout en capturant l'argument en vue d'une vérification supplémentaire?

22
Hilikus

Je recommande d'utiliser l'intégration Hamcrest de Mockito pour écrire un bon et propre matcher. Cela vous permet de combiner la vérification avec la vérification détaillée de l'argument passé:

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

Les correspondants entraînent généralement des tests plus lisibles et des messages d'échec de test plus utiles. Ils ont également tendance à être très réutilisables et vous devrez en constituer une bibliothèque sur mesure pour tester votre projet. Enfin, vous pouvez également les utiliser pour des assertions de test normales à l'aide de la fonction Assert.assertThat() de JUnit, de sorte que vous en obtiendrez une double utilisation.

26
Ryan Stewart

Citant les docs:

Notez qu'une ArgumentCaptor ne fait aucune vérification de type , c'est seulement là pour éviter de jeter dans votre code. Cela pourrait toutefois changer (des contrôles de type Pourraient être ajoutés) dans une future version majeure.

Je ne voudrais pas utiliser un ArgumentCaptor pour cela. Cette classe capture (littéralement) tout, malgré la classe fournie car c'est l'argument .forClass

Pour obtenir ce que vous voulez, je suggère d'intercepter l'argument à l'aide de l'interface Answer de Mockito:

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
Marlon Bernardes

Vous pouvez utiliser le capteur pour capturer, puis vérifier le nombre d'appels avec chaque type d'argument séparément. 

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

Apparemment, le type générique de la référence de capteur n'affecte en rien l'exécution.

4
Keith

J'ai aussi rencontré ce problème aujourd'hui. Je pensais que je pouvais simplement faire quelque chose comme

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

mais je ne pouvais pas le faire fonctionner. J'ai fini avec cette solution:

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

Remarque: si une seule classe doit être capturée de cette manière, typeCheckedValues peut être simplifié en:

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