Comment affirmer qu'un Iterable contient des éléments avec une certaine propriété?

103

Supposons que je souhaite tester une méthode avec cette signature:

List<MyItem> getMyItems();

Présumer MyItem c'est un Pojo qui a de nombreuses propriétés, dont l'une est "name"accessible via getName().

Tout ce que je tiens à vérifier, c'est que le List<MyItem>, ou un Iterable, contient deux MyIteminstances, dont"name" propriétés ont les valeurs "foo"et "bar". Si d'autres propriétés ne correspondent pas, je ne me soucie pas vraiment des objectifs de ce test. Si les noms correspondent, c'est un test réussi.

Je voudrais que ce soit une ligne si possible. Voici une "pseudo-syntaxe" du genre de chose que je voudrais faire.

assert(listEntriesMatchInAnyOrder(myClass.getMyItems(), property("name"), new String[]{"foo", "bar"});

Hamcrest serait-il bon pour ce genre de chose? Si oui, quelle serait exactement la version hamcrest de ma pseudo-syntaxe ci-dessus?

Kevin Pauli
la source

Réponses:

125

Merci @Razvan qui m'a pointé dans la bonne direction. J'ai pu l'obtenir en une seule ligne et j'ai réussi à traquer les importations pour Hamcrest 1.3.

les importations:

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.beans.HasPropertyWithValue.hasProperty;

le code:

assertThat( myClass.getMyItems(), contains(
    hasProperty("name", is("foo")), 
    hasProperty("name", is("bar"))
));
Kevin Pauli
la source
49

Essayer:

assertThat(myClass.getMyItems(),
                          hasItem(hasProperty("YourProperty", is("YourValue"))));
Razvan
la source
2
juste comme un nœud secondaire - c'est une solution hamcrest (pas assertj)
Hartmut P.
46

Ce n'est pas spécialement Hamcrest, mais je pense qu'il vaut la peine de le mentionner ici. Ce que j'utilise assez souvent en Java8, c'est quelque chose comme:

assertTrue(myClass.getMyItems().stream().anyMatch(item -> "foo".equals(item.getName())));

(Modifié à la légère amélioration de Rodrigo Manyari. C'est un peu moins verbeux. Voir les commentaires.)

C'est peut-être un peu plus difficile à lire, mais j'aime le type et la sécurité de refactoring. C'est également cool pour tester plusieurs propriétés de haricot en combinaison. par exemple avec une expression && de type java dans le filtre lambda.

Mario Eis
la source
2
Légère amélioration: assertTrue (myClass.getMyItems (). Stream (). AnyMatch (item -> "foo" .equals (item.getName ()));
Rodrigo Manyari
@RodrigoManyari, parenthèse fermante manquante
Abdull
1
Cette solution gaspille la possibilité d'afficher un message d'erreur approprié.
Giulio Caccin
@GiulioCaccin Je ne pense pas que ce soit le cas. Si vous utilisez JUnit, vous pouvez / devez utiliser les méthodes d'assertion surchargées et écrire assertTrue (..., "My own test failure message"); En savoir plus sur junit.org/junit5/docs/current/api/org/junit/jupiter/api
Mario Eis
Je veux dire, si vous faites l'assertion contre un booléen, vous perdez la possibilité d'imprimer automatiquement la différence réelle / attendue. Il est possible de faire une affirmation à l'aide d'un matcher, mais vous devez modifier cette réponse pour qu'elle soit similaire aux autres de cette page pour le faire.
Giulio Caccin
20

Assertj est bon dans ce domaine.

import static org.assertj.core.api.Assertions.assertThat;

    assertThat(myClass.getMyItems()).extracting("name").contains("foo", "bar");

Le gros plus pour assertj par rapport à hamcrest est l'utilisation facile de la complétion de code.

Frank Neblung
la source
16

AssertJ fournit une excellente fonctionnalité dans extracting(): vous pouvez passer des Functions pour extraire des champs. Il fournit une vérification au moment de la compilation.
Vous pouvez également affirmer la taille en premier facilement.

Cela donnerait:

import static org.assertj.core.api.Assertions;

Assertions.assertThat(myClass.getMyItems())
          .hasSize(2)
          .extracting(MyItem::getName)
          .containsExactlyInAnyOrder("foo", "bar"); 

containsExactlyInAnyOrder() affirme que la liste ne contient que ces valeurs quel que soit l'ordre.

Pour affirmer que la liste contient ces valeurs quel que soit l'ordre mais peut également contenir d'autres valeurs, utilisez contains():

.contains("foo", "bar"); 

En remarque: pour affirmer plusieurs champs à partir d'éléments de a List, avec AssertJ, nous le faisons en enveloppant les valeurs attendues pour chaque élément dans une tuple()fonction:

import static org.assertj.core.api.Assertions;
import static org.assertj.core.groups.Tuple;

Assertions.assertThat(myClass.getMyItems())
          .hasSize(2)
          .extracting(MyItem::getName, MyItem::getOtherValue)
          .containsExactlyInAnyOrder(
               tuple("foo", "OtherValueFoo"),
               tuple("bar", "OtherValueBar")
           ); 
davidxxx
la source
4
Ne comprenez pas pourquoi cela n'a pas de votes positifs. Je pense que c'est de loin la meilleure réponse.
PeMa
1
La bibliothèque assertJ est beaucoup plus lisible que l'API d'assertion JUnit.
Sangimed
@Sangimed D'accord et aussi je préfère cela à hamcrest.
davidxxx
À mon avis, c'est un peu moins lisible car il sépare la «valeur réelle» de la «valeur attendue» et les met dans un ordre qui doit correspondre.
Terran
5

Tant que votre List est une classe concrète, vous pouvez simplement appeler la méthode contains () tant que vous avez implémenté votre méthode equals () sur MyItem.

// given 
// some input ... you to complete

// when
List<MyItems> results = service.getMyItems();

// then
assertTrue(results.contains(new MyItem("foo")));
assertTrue(results.contains(new MyItem("bar")));

Supposons que vous ayez implémenté un constructeur qui accepte les valeurs sur lesquelles vous souhaitez affirmer. Je me rends compte que ce n'est pas sur une seule ligne, mais il est utile de savoir quelle valeur manque plutôt que de vérifier les deux à la fois.

Brad
la source
1
J'aime vraiment votre solution, mais devrait-il modifier tout ce code pour un test?
Kevin Bowersox
Je pense que chaque réponse ici nécessitera une configuration de test, l'exécution de la méthode à tester, puis affirmera les propriétés. Il n'y a pas de réel frais généraux à ma réponse d'après ce que je peux voir, seulement que j'ai deux affirmations sur les lignes seaprate afin qu'une assertion ratée puisse clairement identifier la valeur manquante.
Brad
Il serait préférable d'inclure également un message dans assertTrue afin que le message d'erreur soit plus intelligible. Sans message, en cas d'échec, JUnit lancera simplement une AssertionFailedError sans aucun message d'erreur. Il est donc préférable d'inclure quelque chose comme "les résultats devraient contenir un nouveau MyItem (\" foo \ ")".
Max
Oui, tu as raison. Je recommanderais Hamcrest dans tous les cas, et je n'utilise jamais assertTrue () ces jours-ci
Brad
À noter que votre POJO ou DTO devrait définir la méthode d'égalité
Tayab Hussain
1

AssertJ 3.9.1 prend en charge l'utilisation directe des prédicats dans la anyMatchméthode.

assertThat(collection).anyMatch(element -> element.someProperty.satisfiesSomeCondition())

C'est un cas d'utilisation généralement approprié pour une condition arbitrairement complexe.

Pour les conditions simples, je préfère utiliser la extractingméthode (voir ci-dessus) car le résultat du test itérable sous test peut prendre en charge la vérification de la valeur avec une meilleure lisibilité. Exemple: il peut fournir une API spécialisée telle que la containsméthode dans la réponse de Frank Neblung. Ou vous pouvez l'appeler anyMatchplus tard de toute façon et utiliser une référence de méthode telle que "searchedvalue"::equals. De plus, plusieurs extracteurs peuvent être mis en extractingméthode, le résultat étant ensuite vérifié à l'aide tuple().

Tomáš Záluský
la source