Comment tester les annotations de validation d’une classe utilisant JUnit?

Je dois tester les annotations de validation, mais il semble qu’elles ne fonctionnent pas. Je ne suis pas sûr si le JUnit est également correct. Actuellement, le test est réussi mais, comme vous pouvez le constater, l’adresse e-mail spécifiée est incorrecte.

JUnit

public static void testContactSuccess() { Contact contact = new Contact(); contact.setEmail("Jackyahoo.com"); contact.setName("Jack"); System.err.println(contact); } 

Classe à tester

 public class Contact { @NotNull @Size(min = 1, max = 10) Ssortingng name; @NotNull @Pattern(regexp="[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\." +"[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@" +"(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", message="{invalid.email}") Ssortingng email; @Digits(fraction = 0, integer = 10) @Size(min = 10, max = 10) Ssortingng phone; getters and setters } 

L’ autre réponse disant que “les annotations ne font rien par eux-mêmes, vous devez utiliser un validateur pour traiter l’object” est correcte, cependant, la réponse manque d’instructions de travail sur la façon de le faire à l’aide d’une instance de validateur, qui était pour moi ce que je voulais vraiment.

Hibernate-validator est l’implémentation de référence d’un tel validateur. Vous pouvez l’utiliser assez proprement comme ceci:

 import static org.junit.Assert.assertFalse; import java.util.Set; import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.ValidatorFactory; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class ContactValidationTest { private static Validator validator; @Before public void setUp() { ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); validator = factory.getValidator(); } @Test public void testContactSuccess() { // I'd name the test to something like // invalidEmailShouldFailValidation() Contact contact = new Contact(); contact.setEmail("Jackyahoo.com"); contact.setName("Jack"); Set> violations = validator.validate(contact); assertFalse(violations.isEmpty()); } } 

Cela suppose que vous avez une implémentation de validateur et Junit comme dépendances.

Exemple de dépendances utilisant Maven pom:

  org.hibernate 5.2.4.Final hibernate-validator   junit junit 4.12 test  

Les annotations ne font rien par elles-mêmes, vous devez utiliser un validateur pour traiter l’object.

Votre test doit exécuter du code comme celui-ci

  Configuration configuration = Validation .byDefaultProvider() .providerResolver( new MyResolverStrategy() ) <== this is where is gets tricky .configure(); ValidatorFactory factory = configuration.buildValidatorFactory(); Contact contact = new Contact(); contact.setEmail("Jackyahoo.com"); contact.setName("Jack"); factory.getValidator().validate(contact); <== this normally gets run in the background by whatever framework you are using 

Cependant, la difficulté que vous rencontrez ici sont toutes les interfaces, vous aurez besoin d'implémentations pour pouvoir tester. Vous pouvez le mettre en œuvre vous-même ou en trouver un à utiliser.

Cependant, la question que vous voulez vous poser est qu'est-ce que vous essayez de tester? That the hibernate validator works the way it should? ou that your regex is correct?

Si c'était moi, je supposerais que le validateur fonctionne (c'est-à-dire que quelqu'un d'autre l'a testé) et se concentre sur la regex. Ce qui impliquerait un peu de reflection

 public void emailRegex(Ssortingng email,boolean validates){ Field field = Contact.class.getDeclaredField("email"); javax.validation.constraints.Pattern[] annotations = field.getAnnotationsByType(javax.validation.constraints.Pattern.class); assertEquals(email.matches(annotations[0].regexp()),validates); } 

alors vous pouvez définir vos méthodes de test qui sont des tests unitaires réels

 @Test public void testInvalidEmail() throws NoSuchFieldException { emailRegex("Jackyahoo.com", false); } @Test public void testValidEmail() throws NoSuchFieldException { emailRegex("[email protected]", true); } @Test public void testNoUpperCase() throws NoSuchFieldException { emailRegex("[email protected]", false); } 

Il y a 2 choses que vous devez vérifier:

Les règles de validation sont configurées correctement

Les règles de validation peuvent être vérifiées comme le conseillent les autres – en créant un object validateur et en l’invoquant manuellement:

 Set violations = validator.validate(contact); assertFalse(violations.isEmpty()); 

Avec cela, vous devriez vérifier tous les cas possibles – il pourrait y en avoir des dizaines (et dans ce cas, il devrait y en avoir des dizaines).

La validation est déclenchée par les frameworks

Dans votre cas, vous le vérifiez avec Hibernate, il devrait donc y avoir un test qui l’initialise et déclenche certaines opérations Hibernate. Notez que pour cela, vous devez vérifier une seule règle défaillante pour un seul champ – cela suffira. Vous n’avez pas besoin de vérifier toutes les règles de nouveau. Exemple pourrait être:

 @Test(expected = ConstraintViolationException.class) public void validationIsInvokedBeforeSavingContact() { Contact contact = Contact.random(); contact.setEmail(invalidEmail()); contactsDao.save(contact) session.flush(); // or entityManager.flush(); } 

NB: n’oubliez pas de déclencher flush() . Si vous utilisez des UUID ou des séquences en tant que stratégie de génération d’identifiant, alors INSERT ne sera pas effacé lorsque vous save() , il sera reporté à plus tard.

Tout cela fait partie de la façon de construire une pyramide de test – vous pouvez trouver plus de détails ici .

tel que:

 public class Test { @Autowired private Validator validator; public void testContactSuccess() { Contact contact = new Contact(); contact.setEmail("Jackyahoo.com"); contact.setName("Jack"); System.err.println(contact); Set> violations = validator.validate(contact); assertTrue(violations.isEmpty()); } } 

et vous avez également besoin d’append bean autowired dans votre fichier context.xml, tel que:

   

Voici ma façon de tester les objects avec des champs annotés avec des contraintes javax.validation.constraints .
Je donnerai un exemple avec Java 8, une entité JPA, Spring Boot et JUnit 5, mais l’idée générale est la même quels que soient le contexte et les frameworks:
Nous avons un scénario nominal où tous les champs sont correctement évalués et généralement plusieurs scénarios d’erreur dans lesquels un ou plusieurs champs ne sont pas correctement évalués.

Tester la validation sur le terrain n’est pas une tâche particulièrement difficile.
Mais comme nous avons beaucoup de champs à valider, les tests peuvent devenir plus complexes, nous pouvons en oublier certains cas, en introduisant des effets secondaires lors de tests entre deux cas pour valider ou tout simplement introduire des doublons.
Je vais réfléchir à la façon d’éviter cela.

Dans le code OP, nous supposerons que les 3 champs ont une contrainte NotNull . Je pense que sous 3 contraintes distinctes, le motif et sa valeur sont moins visibles.

J’ai d’abord écrit un test unitaire pour le scénario nominal:

 import org.junit.jupiter.api.Test; @Test public void persist() throws Exception { Contact contact = createValidContact(); // action contactRepository.save(contact); entityManager.flush(); entityManager.clear(); // assertion on the id for example ... } 

J’extrais le code pour créer un contact valide dans une méthode car cela ne sera utile que dans des cas non nominaux:

 private Contact createValidContact(){ Contact contact = new Contact(); contact.setEmail("Jackyahoo.com"); contact.setName("Jack"); contact.setPhone("33999999"); return contact; } 

Maintenant, j’écris un @parameterizedTest avec comme source de fixture une méthode @MethodSource :

 import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import javax.validation.ConstraintViolationException; @ParameterizedTest @MethodSource("persist_fails_with_constraintViolation_fixture") void persist_fails_with_constraintViolation(Contact contact ) { assertThrows(ConstraintViolationException.class, () -> { contactRepository.save(contact); entityManager.flush(); }); } 

Pour comstackr / exécuter @parameterizedTest , pensez à append la dépendance requirejse qui n’est pas incluse dans la dépendance junit-jupiter-api:

  org.junit.jupiter junit-jupiter-params ${junit-jupiter.version} test  

Dans la méthode de assembly pour créer des contacts non valides, l’idée est simple. Pour chaque cas, je crée un nouvel object contact valide et je ne spécifie pas correctement le champ à valider.
De cette manière, je m’assure qu’aucun effet secondaire entre les cas n’est présent et que chaque cas se provoque l’exception de validation attendue car sans le champ défini, le contact valide a été conservé avec succès.

 private static Stream persist_fails_with_constraintViolation_fixture() { Contact contactWithNullName = createValidContact(); contactWithNullName.setName(null); Contact contactWithNullEmail = createValidContact(); contactWithNullEmail.setEmail(null); Contact contactWithNullPhone = createValidContact(); contactWithNullPhone.setPhone(null); return Stream.of(contactWithNullName, contactWithNullEmail, contactWithNullPhone); } 

Voici le code de test complet:

 import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import javax.validation.ConstraintViolationException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; import org.springframework.test.context.junit.jupiter.SpringExtension; @DataJpaTest @ExtendWith(SpringExtension.class) public class ContactRepositoryTest { @Autowired private TestEntityManager entityManager; @Autowired private ContactRepository contactRepository; @BeforeEach public void setup() { entityManager.clear(); } @Test public void persist() throws Exception { Contact contact = createValidContact(); // action contactRepository.save(contact); entityManager.flush(); entityManager.clear(); // assertion on the id for example ... } @ParameterizedTest @MethodSource("persist_fails_with_constraintViolation_fixture") void persist_fails_with_constraintViolation(Contact contact ) { assertThrows(ConstraintViolationException.class, () -> { contactRepository.save(contact); entityManager.flush(); }); } private static Stream persist_fails_with_constraintViolation_fixture() { Contact contactWithNullName = createValidContact(); contactWithNullName.setName(null); Contact contactWithNullEmail = createValidContact(); contactWithNullEmail.setEmail(null); Contact contactWithNullPhone = createValidContact(); contactWithNullPhone.setPhone(null); return Stream.of(contactWithNullName, contactWithNullEmail, contactWithNullPhone); } } 

Je pense que les validations fonctionneraient après l’appel de méthodes prédéfinies, généralement effectuées par les conteneurs, mais pas immédiatement après l’appel des parameters de l’object. À partir du lien de documentation que vous avez partagé:

> Par défaut, le fournisseur de persistance procède automatiquement à la validation des entités avec des champs persistants ou des propriétés annotées avec des contraintes de validation de bean immédiatement après les événements de cycle de vie PrePersist, PreUpdate et PreRemove.