web-dev-qa-db-fra.com

Mockito et Hamcrest: comment vérifier l'invocation de l'argument Collection?

Je rencontre un problème de génériques avec Mockito et Hamcrest.

S'il vous plaît assumer l'interface suivante:

public interface Service {
    void perform(Collection<String> elements);
}

Et l'extrait de test suivant:

Service service = mock(Service.class);

// ... perform business logic

verify(service).perform(Matchers.argThat(contains("a", "b")));

Je souhaite donc vérifier que ma logique métier a bien appelé le service avec une collection contenant "a" et "b" dans cet ordre.

Cependant, le type de retour de contains(...) est Matcher<Iterable<? extends E>>, donc Matchers.argThat(...) renvoie Iterable<String> dans mon cas, ce qui naturellement ne s'applique pas au Collection<String> requis.

Je sais que je pourrais utiliser un capteur d’arguments tel que proposé dans Hamcrest hasItem et Mockito vérifient l’incohérence , mais j’aimerais bien ne pas le faire.

Toutes les suggestions! Merci!

32
Philipp Jardas

Vous pouvez simplement écrire

verify(service).perform((Collection<String>) Matchers.argThat(contains("a", "b")));

Du point de vue du compilateur, cela transforme un Iterable<String> en un Collection<String>, ce qui est correct, car ce dernier est un sous-type du premier. Au moment de l'exécution, argThat renverra null, de sorte qu'il peut être passé à perform sans ClassCastException. Le point important à ce sujet est que le correcteur accède à la structure interne des arguments de vérification de Mockito, comme le fait argThat.

27
Dawood ibn Kareem

Si vous vous retrouvez dans de telles situations, n’oubliez pas que vous pouvez écrire un très petit adaptateur réutilisable.

verify(service).perform(argThat(isACollectionThat(contains("foo", "bar"))));

private static <T> Matcher<Collection<T>> isACollectionThat(
    final Matcher<Iterable<? extends T>> matcher) {
  return new BaseMatcher<Collection<T>>() {
    @Override public boolean matches(Object item) {
      return matcher.matches(item);
    }

    @Override public void describeTo(Description description) {
      matcher.describeTo(description);
    }
  };
}

Notez que la solution de David ci-dessus, avec le casting, est la réponse la plus courte et correcte.

7
Jeff Bowman

Comme alternative, on pourrait changer l'approche en ArgumentCaptor:

@SuppressWarnings("unchecked") // needed because of `List<String>.class` is not a thing
// suppression can be worked around by using @Captor on a field
ArgumentCaptor<List<String>> captor = ArgumentCaptor.forClass(List.class);

verify(service).perform(captor.capture());
assertThat(captor.getValue(), contains("a", "b"));
5
TWiStErRob

Pourquoi ne pas simplement vérifier avec les arguments attendus, en supposant que la liste ne contienne que les deux éléments, par exemple:

final List<String> expected = Lists.newArrayList("a", "b");
verify(service).perform(expected);

Bien que je sois d’accord avec le principe d’Eugen, je pense que s’appuyer sur des équivalents pour la comparaison de chaînes est acceptable… De plus, le matriciel contains utilise des égaux pour la comparaison.

1
Jonathan

Vous pouvez mettre votre propre lambda en tant que ArgumentMatcher

when(myClass.myMethod(argThat(arg -> arg.containsAll(asList(1,2))))
    .thenReturn(...);
0
dehasi

Vous pouvez avoir votre propre implémentation Java.util.Collection et écraser la méthode equals comme ci-dessous. 

public interface Service {
    void perform(Collection<String> elements);
}

@Test
public void testName() throws Exception {
    Service service = mock(Service.class);
    service.perform(new HashSet<String>(Arrays.asList("a","b")));
    Mockito.verify(service).perform(Matchers.eq(new CollectionVerifier<String>(Arrays.asList("a","b"))));
}

public class CollectionVerifier<E> extends ArrayList<E> {

    public CollectionVerifier() {

    }

    public CollectionVerifier(final Collection<? extends E> c) {
        super(c);
    }

    @Override
    public boolean equals(final Object o) {
        if (o instanceof Collection<?>) {
            Collection<?> other = (Collection<?>) o;
                return this.size() == other.size() && this.containsAll(other);
        }
        return false;
    }

}
0
Dev Blanked