web-dev-qa-db-fra.com

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)
    String 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}")
    String email;

    @Digits(fraction = 0, integer = 10)
    @Size(min = 10, max = 10)
    String phone;

    getters and setters

}
18
Jack

La autre réponse disant que "les annotations ne font rien d’elles-mêmes, vous devez utiliser un Validator pour traiter l’objet" est correcte, cependant, la réponse manque d’instructions de travail sur la procédure à suivre en utilisant une instance de Validator, ce qui pour moi était 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 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<ConstraintViolation<Contact>> 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:

<dependency>
    <groupId>org.hibernate</groupId>
    <version>5.2.4.Final</version>
    <artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
42
eis

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

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 souhaitez 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 réflexion

public void emailRegex(String 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 testMethods 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);
}
4
BevynQ

Voici ma façon de tester les objets avec des champs annotés avec des contraintes javax.validation.constraints.
Je vais donner un exemple avec Java 8, les entités 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 où 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 de nombreux champs à valider, les tests peuvent devenir plus complexes, nous pouvons en oublier certains cas, en introduisant des effets indésirables lors des 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 en aucun cas nominal:

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 compiler/exécuter @parameterizedTest, pensez à ajouter la dépendance requise qui n'est pas incluse dans la dépendance junit-jupiter-api:

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-params</artifactId>
    <version>${junit-jupiter.version}</version>
    <scope>test</scope>
</dependency>

Dans la méthode de montage pour créer des contacts non valides, l'idée est simple. Pour chaque cas, je crée un nouvel objet 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 la zone définie, le contact valide a été conservé avec succès.

private static Stream<Contact> 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<Contact> 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);
    }
}
1
davidxxx

Merci d'abord @Eis pour la réponse , cela m'a aidé. C'est un bon moyen de rater le test, mais je voulais un comportement un peu plus "réaliste". Au moment de l'exécution, une exception serait levée et j'ai donc proposé ceci:

/**
 * Simulates the behaviour of bean-validation e.g. @NotNull
 */
private void validateBean(Object bean) throws AssertionError {
    Optional<ConstraintViolation<Object>> violation = validator.validate(bean).stream().findFirst();
    if (violation.isPresent()) {
        throw new ValidationException(violation.get().getMessage());
    }
}

Avoir une entité avec validation:

@Data
public class MyEntity {

@NotBlank(message = "Name cannot be empty!")
private String name;

}

Dans un test, vous pouvez passer une instance avec des attributs non valides et attendre une exception:

private Validator validator;

@Before
public void setUp() {
    ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
    validator = factory.getValidator();
}

@Test(expected = ValidationException.class)
public void testValidationWhenNoNameThenThrowException() {
    validateBean(new Entity.setName(""));
}
1
LazR

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<ConstraintViolation<Contact>> violations = validator.validate(contact);
        assertTrue(violations.isEmpty());
    }
}

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

<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
</bean>
0
chengpohi