[JAVA] Test de validation de classe de formulaire avec Spring Boot

J'étais confus au sujet de la validation (qui peut être définie par annotation) du côté javax et de la méthode de test de validation du côté Spring, je vais donc le résumer comme suit. Au fait, vous pensez peut-être que vous devriez tout faire avec des annotations personnalisées du côté javax, mais je n'ai pas pu le tester comme prévu, alors j'ai dû le faire du côté Spring.

Comment accéder à un champ dans votre propre validation lors de l'exécution d'un test

Chose que tu veux faire

Commencez par vérifier la classe cible.

PasswordForm.java


package com.ssp_engine.user.domain.model;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

import org.hibernate.validator.constraints.Length;

import com.ssp_engine.user.domain.model.validation.ConfirmPassword;
import com.ssp_engine.user.domain.model.validation.ValidGroup1;
import com.ssp_engine.user.domain.model.validation.ValidGroup2;
import com.ssp_engine.user.domain.model.validation.ValidGroup3;
import com.ssp_engine.user.domain.model.validation.ValidGroup4;

import lombok.Data;

@Data
@ConfirmPassword(field = "password", groups = ValidGroup4.class)
public class PasswordForm {

	@NotBlank(groups = ValidGroup1.class)
	private String currentPassword;

	@NotBlank(groups = ValidGroup1.class)
	@Length(min = 4, max = 8, groups = ValidGroup2.class)
	@Pattern(regexp="^[a-zA-Z0-9]+$", groups = ValidGroup3.class)
	private String password;

	@NotBlank(groups = ValidGroup1.class)
	private String confirmPassword;

}

Il s'agit d'une classe de formulaire pour modifier votre propre mot de passe sur l'écran de gestion d'un service Web général comme celui-ci. currentPassword est le mot de passe actuellement connecté password est le mot de passe à changer confirmPassword est le mot de passe de confirmation

Ainsi, chacun a la validation des expressions régulières @ NotBlank et @ Pattern. Il y a une validation dans currentPassword pour comparer avec le mot de passe actuellement connecté, qui est comme suit.

LoginPassAndFormPassValidator.java


package com.ssp_engine.user.domain.model.validation;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

import com.ssp_engine.user.domain.model.PasswordForm;

@Component
public class LoginPassAndFormPassValidator implements Validator {

	@Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public boolean supports(Class<?> clazz) {
        return PasswordForm.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
    	PasswordForm form = (PasswordForm) target;
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    	UserDetails principal = (UserDetails) auth.getPrincipal();
    	String userPass = principal.getPassword();

        if (form.getCurrentPassword() == null) {
            return;
        }

        if (!this.passwordEncoder.matches(form.getCurrentPassword(), userPass)) {
            errors.rejectValue("currentPassword",
					           "LoginPassAndFormPassValidator.PasswordForm.currentPassword",
					           "Il est différent du mot de passe auquel vous êtes connecté.");
        }
    }
}

Côté contrôleur, je l'utilise après @ InitBinder. Nous testerons ces validateurs Spring et Javax.

tester

Et comme pour le test, il est plus facile de séparer le contenu du test car il n'est plus long de séparer le test côté contrôleur et le test de la classe de formulaire, donc ça ressemble à ceci.

PasswordFormTests.java



@RunWith(SpringRunner.class)
@SpringBootTest
public class PasswordFormTests {

    private PasswordForm passwordForm = new PasswordForm();
    private BindingResult bindingResult = new BindException(passwordForm, "PasswordForm"); //①

    @Autowired
    @Qualifier("loginPassAndFormPassValidator") //②
    /*Côté ressort*/
	private org.springframework.validation.Validator loginPassAndFormPassValidator;
    /*côté javax*/
    private static Validator validator; //③

    @BeforeClass
processus d'initialisation public static void() { //④
        ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory(); 
        validator = validatorFactory.getValidator(); 
    }

    @Before
public void Définir la valeur() throws Exception{ //⑤
    	this.passwordForm.setCurrentPassword("currentpassword");
    	this.passwordForm.setConfirmPassword("password");
    	this.passwordForm.setPassword("password");
    }

}

① ・ ・ ・ Champ de réception du résultat après l'exécution du validateur ② ・ ・ ・ Puisqu'il était nécessaire de spécifier explicitement quelle classe, elle a été spécifiée avec @ Qualifier. ③ ・ ④ ・ ・ ・ Je voulais obtenir un bean en le spécifiant avec @ AutoWired, mais cela n'a pas fonctionné, donc je crée explicitement un bean. ⑤ ・ ・ ・ Une valeur est définie pour l'objet cible.

Quand je serai prêt, je vais faire un test gorigori, et ça ressemble à ça. Vérifiez d'abord qu'il n'y a pas d'erreurs.

PasswordFormTests.java


    @Test
    @WithMockUser(username = "username",
    			  password ="$2a$10$p3/Malw3/KWyfOlPwWoUCulx4iDb2C/nmo6x8P2svXjfJQ5ETLhG2",
    			  roles = "USER")
public void aucune erreur() throws Exception{
    	loginPassAndFormPassValidator.validate(this.passwordForm, bindingResult); //①
        Set<ConstraintViolation<PasswordForm>> violations =
        		validator.validate(this.passwordForm,ValidGroup1.class,ValidGroup2.class,ValidGroup3.class,ValidGroup4.class); //②
        assertThat(bindingResult.getFieldError(), is(nullValue())); //③
        assertThat(violations.size(), is(0)); //④
    }

① ・ ・ ・ La validation côté Spring est en cours d'exécution. L'objet cible est spécifié dans le premier argument et l'objet bindingResult qui stocke le résultat est spécifié dans le deuxième argument. ② ・ ・ ・ ConstraintViolation renvoie un ensemble d'objets qui stockent le contenu de la violation de contrainte, et l'objet cible est le premier argument de validate. Puisque ValidGroup a été spécifié dans le deuxième argument, la validation activée est spécifiée. ② ・ ・ ・ Reçoit le résultat du côté Spring avec bindingResult et vérifie s'il est Null. ③ ・ ・ ・ Le résultat du côté Javax est dimensionné avec violations.size () et vérifié pour voir s'il est égal à 0.

@ WithMockUser est dans l'état connecté car il était nécessaire d'obtenir les informations de connexion avec loginPassAndFormPassValidator.

Quand j'ai découvert qu'il n'y avait pas d'erreur, je l'ai écrit comme ça.

PasswordFormTests.java


    @Test
    @WithMockUser(username = "username",
    			  password ="currentpassword",
    			  roles = "USER")
public void Le chemin de connexion et le chemin d'entrée sont différents() throws Exception{
    	loginPassAndFormPassValidator.validate(this.passwordForm, bindingResult);
        assertThat(bindingResult.getFieldError("currentPassword"), is(bindingResult.getFieldError()));
        assertThat(bindingResult.getFieldError().getDefaultMessage(), is("Il est différent du mot de passe auquel vous êtes connecté."));
    }

    @Test
public void Le mot de passe actuel est vide() throws Exception{
    	this.passwordForm.setCurrentPassword("");
        Set<ConstraintViolation<PasswordForm>> violations =
        		validator.validate(this.passwordForm,ValidGroup1.class);
        assertThat(violations.size(), is(1));
        assertThat(getAnnotation(violations, "currentPassword"), is(instanceOf(NotBlank.class))); //①
    }

    private Annotation getAnnotation(Set<ConstraintViolation<PasswordForm>> violations, String path) { //②
        return violations.stream()
                .filter(cv -> cv.getPropertyPath().toString().equals(path))
                .findFirst()
                .map(cv -> cv.getConstraintDescriptor().getAnnotation())
                .get();
    }

① ・ ・ ・ Vérifiez quelle annotation est à l'origine de l'erreur. (2) ... Je crée une méthode pour obtenir une instance de l'annotation qui a été lue avec une erreur.

Vue d'ensemble

L'image entière ressemble à ceci.

PasswordFormTests.java



package com.ssp_engine.user.domain.model;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import java.lang.annotation.Annotation;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

import org.hibernate.validator.constraints.Length;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;

import com.ssp_engine.user.domain.model.validation.ConfirmPassword;
import com.ssp_engine.user.domain.model.validation.ValidGroup1;
import com.ssp_engine.user.domain.model.validation.ValidGroup2;
import com.ssp_engine.user.domain.model.validation.ValidGroup3;
import com.ssp_engine.user.domain.model.validation.ValidGroup4;


@RunWith(SpringRunner.class)
@SpringBootTest
public class PasswordFormTests {

    private PasswordForm passwordForm = new PasswordForm();
    private BindingResult bindingResult = new BindException(passwordForm, "PasswordForm");

    @Autowired
    @Qualifier("loginPassAndFormPassValidator")
    /*Côté ressort*/
	private org.springframework.validation.Validator loginPassAndFormPassValidator;
    /*côté javax*/
    private static Validator validator;

    @BeforeClass
processus d'initialisation public static void() {
        ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
        validator = validatorFactory.getValidator();
    }

    @Before
public void Définir la valeur() throws Exception{
    	this.passwordForm.setCurrentPassword("currentpassword");
    	this.passwordForm.setConfirmPassword("password");
    	this.passwordForm.setPassword("password");
    }

    @Test
    @WithMockUser(username = "username",
    			  password ="$2a$10$p3/Malw3/KWyfOlPwWoUCulx4iDb2C/nmo6x8P2svXjfJQ5ETLhG2",
    			  roles = "USER")
public void aucune erreur() throws Exception{
    	loginPassAndFormPassValidator.validate(this.passwordForm, bindingResult);
        Set<ConstraintViolation<PasswordForm>> violations =
        		validator.validate(this.passwordForm,ValidGroup1.class,ValidGroup2.class,ValidGroup3.class,ValidGroup4.class);
        assertThat(bindingResult.getFieldError(), is(nullValue()));
        assertThat(violations.size(), is(0));
    }

    @Test
    @WithMockUser(username = "username",
    			  password ="currentpassword",
    			  roles = "USER")
public void Le chemin de connexion et le chemin d'entrée sont différents() throws Exception{
    	loginPassAndFormPassValidator.validate(this.passwordForm, bindingResult);
        assertThat(bindingResult.getFieldError("currentPassword"), is(bindingResult.getFieldError()));
        assertThat(bindingResult.getFieldError().getDefaultMessage(), is("Il est différent du mot de passe auquel vous êtes connecté."));
    }

    @Test
public void Le mot de passe actuel est vide() throws Exception{
    	this.passwordForm.setCurrentPassword("");
        Set<ConstraintViolation<PasswordForm>> violations =
        		validator.validate(this.passwordForm,ValidGroup1.class);
        assertThat(violations.size(), is(1));
        assertThat(getAnnotation(violations, "currentPassword"), is(instanceOf(NotBlank.class)));
    }

    @Test
Le mot de passe public void est vide() throws Exception{
        this.passwordForm.setPassword("");
        Set<ConstraintViolation<PasswordForm>> violations =
        		validator.validate(this.passwordForm,ValidGroup1.class);
        assertThat(violations.size(), is(1));
        assertThat(getAnnotation(violations, "password"), is(instanceOf(NotBlank.class)));
    }

    @Test
public void Le mot de passe de confirmation est vide() throws Exception{
        this.passwordForm.setConfirmPassword("");
        Set<ConstraintViolation<PasswordForm>> violations =
        		validator.validate(this.passwordForm,ValidGroup1.class);
        assertThat(violations.size(), is(1));
        assertThat(getAnnotation(violations, "confirmPassword"), is(instanceOf(NotBlank.class)));
    }

    @Test
public void Lorsque le mot de passe de confirmation et le mot de passe d'entrée sont différents() throws Exception{
        this.passwordForm.setPassword("aiueo");
        this.passwordForm.setConfirmPassword("kakikukeko");
        Set<ConstraintViolation<PasswordForm>> violations =
        		validator.validate(this.passwordForm,ValidGroup4.class);
        assertThat(violations.size(), is(1));
        assertThat(getAnnotation(violations, "password"), is(instanceOf(ConfirmPassword.class)));
    }

    @Test
public void Lorsque le mot de passe contient 8 caractères ou plus() throws Exception{
    	this.passwordForm.setPassword("aiueokakikukeko");
        Set<ConstraintViolation<PasswordForm>> violations =
        		validator.validate(this.passwordForm,ValidGroup2.class);
        assertThat(violations.size(), is(1));
        assertThat(getAnnotation(violations, "password"), is(instanceOf(Length.class)));
    }

    @Test
public void Lorsque le mot de passe est de 4 caractères ou moins() throws Exception{
    	this.passwordForm.setPassword("aiu");
        Set<ConstraintViolation<PasswordForm>> violations =
        		validator.validate(this.passwordForm,ValidGroup2.class);
        assertThat(violations.size(), is(1));
        assertThat(getAnnotation(violations, "password"), is(instanceOf(Length.class)));
    }

    @Test
public void Lorsque le mot de passe est autre que des caractères alphanumériques demi-largeur() throws Exception{
    	this.passwordForm.setPassword("C'est un test");
        Set<ConstraintViolation<PasswordForm>> violations =
        		validator.validate(this.passwordForm,ValidGroup3.class);
        assertThat(violations.size(), is(1));
        assertThat(getAnnotation(violations, "password"), is(instanceOf(Pattern.class)));
    }

    private Annotation getAnnotation(Set<ConstraintViolation<PasswordForm>> violations, String path) {
        return violations.stream()
                .filter(cv -> cv.getPropertyPath().toString().equals(path))
                .findFirst()
                .map(cv -> cv.getConstraintDescriptor().getAnnotation())
                .get();
    }
}

Je serais reconnaissant si quelqu'un pouvait le trouver utile.

Recommended Posts

Test de validation de classe de formulaire avec Spring Boot
Obtenez des résultats de validation avec Spring Boot
Forme de botte de printemps
Effectuer un test de confirmation de transaction avec Spring Boot
Télécharger avec Spring Boot
Testez le contrôleur avec Mock MVC dans Spring Boot
[Java] Article pour ajouter une validation avec Spring Boot 2.3.1.
Générer un code à barres avec Spring Boot
Hello World avec Spring Boot
Démarrez avec Spring Boot
[Compatible JUnit 5] Ecrire un test en utilisant JUnit 5 avec Spring boot 2.2, 2.3
Bonjour tout le monde avec Spring Boot!
Exécutez LIFF avec Spring Boot
Connexion SNS avec Spring Boot
Spring Boot à partir de Docker
Testez la classe injectée sur le terrain dans le test de démarrage Spring sans utiliser le conteneur Spring
Hello World avec Spring Boot
Définir des cookies avec Spring Boot
Utiliser Spring JDBC avec Spring Boot
Ajouter un module avec Spring Boot
Premiers pas avec Spring Boot
Créer un micro service avec Spring Boot
Test de validation d'élément unique de printemps
Envoyer du courrier avec Spring Boot
Modifier le message de validation Spring Boot
Utiliser l'authentification de base avec Spring Boot
gRPC sur Spring Boot avec grpc-spring-boot-starter
Déploiement à chaud avec le développement Spring Boot
Écrire du code de test avec Spring Boot
Programmation Spring Boot avec VS Code
Jusqu'à "Hello World" avec Spring Boot
Créer une application d'enquête avec Spring Boot
(Intellij) Hello World avec Spring Boot
Google Cloud Platform avec Spring Boot 2.0.0
Utiliser DBUnit pour le test Spring Boot
J'ai essayé GraphQL avec Spring Boot
[Java] Intégration LINE avec Spring Boot
À partir de Spring Boot 0. Utilisez Spring CLI
J'ai essayé Flyway avec Spring Boot
Contrôleur de cadre de test Spring avec Junit
La coopération des messages a commencé avec Spring Boot
J'ai créé un formulaire de recherche simple avec Spring Boot + GitHub Search API.
Exemple de code pour le test unitaire d'un contrôleur Spring Boot avec MockMvc
[Spring Boot] Jusqu'à ce que @Autowired soit exécuté dans la classe de test [JUnit5]
Traitement lors du démarrage d'une application avec Spring Boot
Hello World avec Eclipse + Spring Boot + Maven
Envoyez des notifications régulières avec LineNotify + Spring Boot
HTTPS avec Spring Boot et Let's Encrypt
Essayez d'utiliser Spring Boot avec VS Code
Le test Spring Boot @WebMvcTest active la sécurité par défaut de Spring Security
Démarrez le développement d'applications Web avec Spring Boot
J'ai essayé l'initialisation paresseuse avec Spring Boot 2.2.0
Implémenter CRUD avec Spring Boot + Thymeleaf + MySQL
Traitement asynchrone avec Spring Boot en utilisant @Async
Implémenter la fonction de pagination avec Spring Boot + Thymeleaf
(IntelliJ + gradle) Hello World avec Spring Boot
Utiliser le cache avec EhCashe 2.x avec Spring Boot
Exécutez l'application WEB avec Spring Boot + Thymeleaf
Obtenez une authentification BASIC avec Spring Boot + Spring Security