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


Je suis confronté à un problème de génériques avec Mockito et Hamcrest.

Veuillez 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 veux donc vérifier que ma logique métier a effectivement 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>>, alors Matchers.argThat(...) résultats Iterable<String> dans mon cas, ce qui ne s'applique naturellement pas à la demande Collection<String>.

Je sais que je pourrais utiliser un argument captor comme proposé dans Hamcrest hasItem et Mockito vérifient les incohérences, mais j'aimerais beaucoup ne pas le faire.

Aucune suggestion! Merci!


28
2017-12-07 12:53


origine


Réponses:


Vous pouvez simplement écrire

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

Du point de vue du compilateur, cela crée un Iterable<String> à un Collection<String> ce qui est bien, car ce dernier est un sous-type du premier. Lors de l'exécution, argThat reviendra null, de sorte que peut être passé à perform sans un ClassCastException. Le point important à ce sujet est que le matcher obtient la structure interne des arguments de Mockito pour vérification, ce qui est ce que argThat Est-ce que.


23
2017-12-11 16:33



Si vous êtes bloqué 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 correcte la plus courte.


5
2017-12-09 19:05



Comme alternative, on pourrait changer l'approche de 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"));

2
2017-09-07 16:04



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 Eugen en principe, je pense que le fait de compter sur des équivalents pour la comparaison de chaînes est acceptable ... contains matcher utilise des égaux pour la comparaison de toute façon.


1
2017-12-10 09:06



Vous pourriez avoir votre propre implémentation java.util.Collection et remplacer la méthode égale à celle 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
2017-12-09 12:30