[JAVA] Formularklassenvalidierungstest mit Spring Boot

Ich war verwirrt über die Validierung (die durch Annotation definiert werden kann), die die Javax-Seite hat, und die Validierungstestmethode auf der Federseite, daher werde ich sie wie folgt zusammenfassen. Übrigens denken Sie vielleicht, dass Sie alles mit benutzerdefinierten Anmerkungen auf der Javax-Seite machen sollten, aber ich konnte es nicht wie erwartet testen, also musste ich es auf der Spring-Seite machen.

So wechseln Sie zu einem Feld in Ihrer eigenen Validierung, wenn Sie einen Test ausführen

Was du machen willst

Überprüfen Sie zunächst die Zielklasse.

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;

}

Dies ist eine Formularklasse zum Bearbeiten Ihres eigenen Kennworts auf dem Verwaltungsbildschirm eines allgemeinen Webdienstes wie diesem. currentPassword ist das aktuell angemeldete Passwort password ist das zu ändernde Passwort verifyPassword ist das Bestätigungspasswort

Jeder hat also eine Validierung der regulären Ausdrücke "@ NotBlank" und "@ Pattern". In currentPassword gibt es eine Validierung, um sie mit dem aktuell angemeldeten Passwort zu vergleichen.

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",
					           "Es unterscheidet sich von dem Passwort, mit dem Sie angemeldet sind.");
        }
    }
}

Auf der Controllerseite benutze ich es nach @ InitBinder. Wir werden diese Frühjahrs- und Javax-Validatoren testen.

Prüfung

Und für den Test ist es einfacher, den Testinhalt zu trennen, da der Test auf der Controllerseite und der Test der Formularklasse nicht mehr getrennt werden müssen. Es sieht also so aus.

PasswordFormTests.java



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

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

    @Autowired
    @Qualifier("loginPassAndFormPassValidator") //②
    /*Federseite*/
	private org.springframework.validation.Validator loginPassAndFormPassValidator;
    /*Javax Seite*/
    private static Validator validator; //③

    @BeforeClass
öffentlicher statischer Void-Initialisierungsprozess() { //④
        ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory(); 
        validator = validatorFactory.getValidator(); 
    }

    @Before
public void Wert festlegen() throws Exception{ //⑤
    	this.passwordForm.setCurrentPassword("currentpassword");
    	this.passwordForm.setConfirmPassword("password");
    	this.passwordForm.setPassword("password");
    }

}

① ・ ・ ・ Feld zum Empfangen des Ergebnisses nach Ausführung des Validators ② ・ ・ ・ Da es notwendig war, explizit anzugeben, welche Klasse, wurde sie mit @ Qualifier angegeben. Wanted ・ ④ ・ ・ ・ Ich wollte eine Bean erhalten, indem ich sie mit @ AutoWired spezifizierte, aber es hat nicht funktioniert, deshalb erstelle ich explizit eine Bean. ⑤ ・ ・ ・ Für das Zielobjekt wird ein Wert festgelegt.

Wenn ich fertig bin, mache ich einen Gorigori-Test und es sieht so aus. Überprüfen Sie zunächst, ob keine Fehler vorliegen.

PasswordFormTests.java


    @Test
    @WithMockUser(username = "username",
    			  password ="$2a$10$p3/Malw3/KWyfOlPwWoUCulx4iDb2C/nmo6x8P2svXjfJQ5ETLhG2",
    			  roles = "USER")
public void kein Fehler() 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)); //④
    }

・ ・ ・ ・ Die Validierung auf der Federseite wird ausgeführt. Das Zielobjekt wird im ersten Argument angegeben, und das Objekt bindingResult, das das Ergebnis speichert, wird im zweiten Argument angegeben. Con ・ ・ ・ ConstraintViolation gibt eine Reihe von Objekten zurück, in denen der Inhalt der Constraint-Verletzung gespeichert ist, und das Zielobjekt ist das erste Argument für validate. Da ValidGroup im zweiten Argument angegeben wurde, wird angegeben, welche Validierung aktiviert ist. ② ・ ・ ・ Empfängt das Ergebnis auf der Federseite mit bindingResult und prüft, ob es Null ist. ③ ・ ・ ・ Das Ergebnis auf der Javax-Seite wird mit rows.size () bemessen und überprüft, ob es 0 ist.

@ WithMockUser befindet sich im angemeldeten Zustand, da die Anmeldeinformationen mit loginPassAndFormPassValidator abgerufen werden mussten.

Als ich herausfand, dass dies kein Fehler war, schrieb ich es so.

PasswordFormTests.java


    @Test
    @WithMockUser(username = "username",
    			  password ="currentpassword",
    			  roles = "USER")
public void Anmeldepfad und Eingabepfad sind unterschiedlich() throws Exception{
    	loginPassAndFormPassValidator.validate(this.passwordForm, bindingResult);
        assertThat(bindingResult.getFieldError("currentPassword"), is(bindingResult.getFieldError()));
        assertThat(bindingResult.getFieldError().getDefaultMessage(), is("Es unterscheidet sich von dem Passwort, mit dem Sie angemeldet sind."));
    }

    @Test
public void Das aktuelle Passwort ist leer() 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();
    }

① ・ ・ ・ Überprüfen Sie, welche Anmerkung den Fehler verursacht. (2) ... Ich erstelle eine Methode, um eine Instanz der Annotation abzurufen, die mit einem Fehler abgespielt wurde.

Gesamtbild

Das ganze Bild sieht so aus.

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")
    /*Federseite*/
	private org.springframework.validation.Validator loginPassAndFormPassValidator;
    /*Javax Seite*/
    private static Validator validator;

    @BeforeClass
öffentlicher statischer Void-Initialisierungsprozess() {
        ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
        validator = validatorFactory.getValidator();
    }

    @Before
public void Wert festlegen() 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 kein Fehler() 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 Anmeldepfad und Eingabepfad sind unterschiedlich() throws Exception{
    	loginPassAndFormPassValidator.validate(this.passwordForm, bindingResult);
        assertThat(bindingResult.getFieldError("currentPassword"), is(bindingResult.getFieldError()));
        assertThat(bindingResult.getFieldError().getDefaultMessage(), is("Es unterscheidet sich von dem Passwort, mit dem Sie angemeldet sind."));
    }

    @Test
public void Das aktuelle Passwort ist leer() 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
public void Passwort ist leer() 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 Bestätigungspasswort ist leer() 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 Wenn das Bestätigungskennwort und das Eingabekennwort unterschiedlich sind() 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 Wenn das Passwort aus 8 Zeichen oder mehr besteht() 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 Wenn das Passwort maximal 4 Zeichen lang ist() 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 Wenn das Kennwort keine alphanumerischen Zeichen mit halber Breite enthält() throws Exception{
    	this.passwordForm.setPassword("Es ist ein 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();
    }
}

Ich wäre dankbar, wenn jemand es hilfreich finden könnte.

Recommended Posts

Formularklassenvalidierungstest mit Spring Boot
Erhalten Sie Validierungsergebnisse mit Spring Boot
Spring Boot Form
Führen Sie einen Transaktionsbestätigungstest mit Spring Boot durch
Mit Spring Boot herunterladen
Testen Sie den Controller mit Mock MVC im Spring Boot
[Java] Artikel zum Hinzufügen einer Validierung mit Spring Boot 2.3.1.
Generieren Sie mit Spring Boot einen Barcode
Hallo Welt mit Spring Boot
Beginnen Sie mit Spring Boot
[JUnit 5-kompatibel] Schreiben Sie einen Test mit JUnit 5 mit Spring Boot 2.2, 2.3
Hallo Welt mit Spring Boot!
Führen Sie LIFF mit Spring Boot aus
SNS-Login mit Spring Boot
Spring Boot beginnend mit Docker
Testen Sie die Klasse mit Feldinjektion im Spring-Boot-Test, ohne den Spring-Container zu verwenden
Hallo Welt mit Spring Boot
Setzen Sie Cookies mit Spring Boot
Verwenden Sie Spring JDBC mit Spring Boot
Modul mit Spring Boot hinzufügen
Erste Schritte mit Spring Boot
Erstellen Sie mit Spring Boot einen Mikrodienst
Frühlingstest zur Überprüfung einzelner Artikel
Mail mit Spring Boot verschicken
Ändern Sie die Spring Boot-Validierungsnachricht
Verwenden Sie die Standardauthentifizierung mit Spring Boot
gRPC auf Spring Boot mit grpc-spring-boot-Starter
Hot Deploy mit Spring Boot-Entwicklung
Schreiben Sie den Testcode mit Spring Boot
Spring Boot Programmierung mit VS Code
Bis "Hallo Welt" mit Spring Boot
Erstellen Sie eine Anfrage-App mit Spring Boot
(Intellij) Hallo Welt mit Spring Boot
Google Cloud Platform mit Spring Boot 2.0.0
Verwenden Sie DBUnit für den Spring Boot-Test
Ich habe GraphQL mit Spring Boot ausprobiert
[Java] LINE-Integration mit Spring Boot
Beginnend mit Spring Boot 0. Verwenden Sie Spring CLI
Ich habe Flyway mit Spring Boot ausprobiert
Testen Sie den Spring Framework Controller mit Junit
Die Nachrichtenkooperation begann mit Spring Boot
Ich habe ein einfaches Suchformular mit Spring Boot + GitHub Search API erstellt.
Beispielcode zum Testen eines Spring Boot-Controllers mit MockMvc
[Spring Boot] Bis @Autowired in der Testklasse [JUnit5] ausgeführt wird
Verarbeitung beim Starten einer Anwendung mit Spring Boot
Hallo Welt mit Eclipse + Spring Boot + Maven
Senden Sie regelmäßige Benachrichtigungen mit LineNotify + Spring Boot
HTTPS mit Spring Boot und Let's Encrypt
Versuchen Sie es mit Spring Boot mit VS-Code
Der Spring Boot @ WebMvcTest-Test aktiviert die Standardsicherheit von Spring Security
Starten Sie die Entwicklung von Webanwendungen mit Spring Boot
Ich habe Lazy Initialization mit Spring Boot 2.2.0 ausprobiert
Implementieren Sie CRUD mit Spring Boot + Thymeleaf + MySQL
Asynchrone Verarbeitung mit Spring Boot unter Verwendung von @Async
Implementieren Sie die Paging-Funktion mit Spring Boot + Thymeleaf
(IntelliJ + gradle) Hallo Welt mit Spring Boot
Verwenden Sie den Cache mit EhCashe 2.x mit Spring Boot
Führen Sie die WEB-Anwendung mit Spring Boot + Thymeleaf aus
Erreichen Sie die BASIC-Authentifizierung mit Spring Boot + Spring Security