Wie teste ich Validierungsannotationen einer Klasse mit JUnit?

Lesezeit: 11 Minuten

Die muss ich testen Validierungsanmerkungen aber es sieht so aus, als ob sie nicht funktionieren. Ich bin mir nicht sicher, ob die JUnit auch korrekt ist. Derzeit wird der Test bestanden, aber wie Sie sehen können, ist die angegebene E-Mail-Adresse falsch.

JUnit

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

Klasse zum Testen

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

}

Benutzer-Avatar
eis

Die andere Antwort, die besagt, dass “die Anmerkungen nichts von selbst tun, Sie müssen einen Validator verwenden, um das Objekt zu verarbeiten”, ist korrekt, der Antwort fehlen jedoch Arbeitsanweisungen, wie dies mit einer Validator-Instanz zu tun ist, was für mich der Fall war was ich wirklich wollte.

Hibernate-Validator ist die Referenzimplementierung eines solchen Validators. Sie können es ganz sauber so verwenden:

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());
    }
}

Dies setzt voraus, dass Sie die Validierungsimplementierung und junit als Abhängigkeiten haben.

Beispiel für Abhängigkeiten mit 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>

  • Wenn man Spring verwendet, kann Validator so injiziert werden: @Autowired private Validator validator;

    – Stanislaw Karachanow

    28. November 2018 um 23:42 Uhr


  • @StanislavKarakhanov ja, wie in dieser Antwort erweitert. Wenn man Spring in diesem Test jedoch nicht bereits verwendet, wird die Spring-Abhängigkeit nicht benötigt.

    – eis

    29. November 2018 um 6:30 Uhr

  • Mit @Autowired of validator müssen Sie den Spring-Kontext starten. Mit ValidatorFactory nicht.

    – Kyrill Ch

    22. Mai 2019 um 21:55 Uhr


  • @HomayounBehzadian Wenn verschachtelte Felder nicht aktiviert sind, haben Sie sie falsch kommentiert. Derselbe Test funktioniert auch für verschachtelte.

    – eis

    16. September 2019 um 17:09 Uhr

  • benötigt eine @Valid-Anmerkung in jedem der Felder

    – Homayoun Behzadian

    17. September 2019 um 5:39 Uhr

Benutzer-Avatar
Oozeerly

Eine einfache Methode zum Testen von Validierungsanmerkungen mit javax:

Deklarieren Sie die Validator auf Klassenebene:

private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

Rufen Sie es dann in Ihrem Test einfach auf object Sie benötigen eine Validierung auf, mit was exception Sie validieren:

Set<TheViolation<TheClassYouAreValidating> violations = validator.validate(theInstanceOfTheClassYouAreValidating);

Dann einfach assert die Anzahl der erwarteten Verstöße:

assertThat(violations.size()).isEqualTo(1);

Sie müssen dies zu Ihren Abhängigkeiten hinzufügen (gradle):

compile group: 'javax.validation', name: 'validation-api', version: '2.0.1.Final'

  • Brauchen Sie nicht auch eine Validierungsimplementierung im Klassenpfad und nicht nur in der API?

    – eis

    16. April 2021 um 18:05 Uhr

Benutzer-Avatar
BevynQ

Die Anmerkungen selbst tun nichts, Sie müssen einen Validator verwenden, um das Objekt zu verarbeiten.

Ihr Test muss einen solchen Code ausführen

    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

Die Schwierigkeit, mit der Sie hier konfrontiert sind, besteht jedoch darin, dass dies alles Schnittstellen sind. Sie benötigen Implementierungen, um sie testen zu können. Sie könnten es selbst implementieren oder eines finden, das Sie verwenden können.

Die Frage, die Sie sich stellen möchten, lautet jedoch: Was möchten Sie testen? That the hibernate validator works the way it should? oder that your regex is correct?

Wenn ich das wäre, würde ich davon ausgehen, dass der Validator funktioniert (dh jemand anderes hat das getestet) und mich auf die Regex konzentrieren. Was ein wenig Nachdenken erfordern würde

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);

}

dann können Sie Ihre testMethods definieren, die eigentliche Unit-Tests sind

@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);
}

  • Die Regex sollte korrekt sein, da ich sie auf der Oracle-Website gefunden habe, aber Ihr Kommentar hat mich zum Nachdenken gebracht, ob sie wirklich funktioniert, wenn der Code ausgeführt wird.

    – Jack

    16. März 2015 um 5:57 Uhr

  • @Jack die Regex scheint Großbuchstaben nicht zuzulassen

    – BevynQ

    16. März 2015 um 6:06 Uhr

Erstmal danke @Eis für die Antwort, hat mir geholfen. Es ist eine gute Möglichkeit, den Test nicht zu bestehen, aber ich wollte ein etwas “lebensechteres” Verhalten. Zur Laufzeit würde eine Ausnahme ausgelöst werden, also kam ich auf Folgendes:

/**
 * 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());
    }
}

Haben Sie eine Entität mit Validierung:

@Data
public class MyEntity {

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

}

In einem Test können Sie eine Instanz mit ungültigen Attributen bestehen und eine Ausnahme erwarten:

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

Benutzer-Avatar
davidxxx

Hier ist mein Weg, meine Objekte mit Feldern zu testen, die mit einigen kommentiert sind javax.validation.constraints Einschränkungen.
Ich werde ein Beispiel mit Java 8, JPA-Entität, Spring Boot und JUnit 5 geben, aber die Gesamtidee ist unabhängig vom Kontext und den Frameworks dieselbe:
Wir haben ein nominelles Szenario, in dem alle Felder korrekt bewertet sind, und im Allgemeinen mehrere Fehlerszenarien, in denen ein oder mehrere Felder nicht korrekt bewertet sind.

Das Testen der Feldvalidierung ist keine besonders schwierige Sache.
Aber da wir viele Felder zu validieren haben, können die Tests komplexer werden, wir können einige Fälle vergessen, Nebenwirkungen in Tests zwischen zwei zu validierenden Fällen einführen oder einfach Duplikation einführen.
Ich werde darüber nachdenken, wie ich das vermeiden kann.

Im OP-Code nehmen wir an, dass die 3 Felder a haben NotNull Zwang. Ich denke, dass unter 3 verschiedenen Einschränkungen das Muster und sein Wert weniger sichtbar sind.

Ich habe zuerst einen Unit-Test für das nominale Szenario geschrieben:

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
     ...
}

Ich extrahiere den Code, um einen gültigen Kontakt in einer Methode zu erstellen, da dies für No-Nominal-Fälle hilfreich ist:

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

Jetzt schreibe ich a @parameterizedTest mit als Fixture Source a @MethodSource Methode :

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();
    });
}

Zum Kompilieren/Ausführen @parameterizedTestdenken Sie daran, die erforderliche Abhängigkeit hinzuzufügen, die nicht in der junit-jupiter-api-Abhängigkeit enthalten ist:

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

Bei der Vorrichtungsmethode zum Erstellen ungültiger Kontakte ist die Idee einfach. Für jeden Fall erstelle ich ein neues gültiges Kontaktobjekt und setze nur das Feld falsch, um das Betreffende zu validieren.
Auf diese Weise stelle ich sicher, dass keine Nebeneffekte zwischen den Fällen vorhanden sind und dass jeder Fall selbst die erwartete Validierungsausnahme provoziert, da ohne das gesetzte Feld der gültige Kontakt erfolgreich beibehalten wurde.

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);
}

Hier ist der vollständige Testcode:

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);
    }
}

  • Ich denke, das funktioniert, weil die NotNull-Annotation einen Einfluss auf die Datenbank selbst hat. Datenbankfelder werden im Datenbankdesign nicht null. In meinem Fall hat meine Entität eine javax.validation.constraints.Pattern-Anmerkung für ein Feld, die keine ConstraintViolationException in einem DataJpaTest verursacht.

    – DCO

    20. Januar um 13:20 Uhr

Benutzer-Avatar
ugrkocak1980

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;

public class ValidationTest {

    private Validator validator;

    @Before
    public void init() {

        ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
        this.validator = vf.getValidator();

    }

    @Test
    public void prereqsMet() {
        Workshop validWorkshop = new Workshop(2, 2, true, 3);
        Set<ConstraintViolation<Workshop>> violations = this.validator.validate(validWorkshop);
        assertTrue(violations.isEmpty());
    }  
}

Genau genommen ist es kein Unit-Test, sondern ein Integrationstest. In Unit Test möchten Sie nur die Validierungslogik testen, ohne Abhängigkeiten zum SPI.

https://www.adam-bien.com/roller/abien/entry/unit_integration_testing_the_bean

  • Ich denke, das funktioniert, weil die NotNull-Annotation einen Einfluss auf die Datenbank selbst hat. Datenbankfelder werden im Datenbankdesign nicht null. In meinem Fall hat meine Entität eine javax.validation.constraints.Pattern-Anmerkung für ein Feld, die keine ConstraintViolationException in einem DataJpaTest verursacht.

    – DCO

    20. Januar um 13:20 Uhr

Es gibt 2 Dinge, die Sie überprüfen müssen:

Die Validierungsregeln sind korrekt konfiguriert

Die Validierungsregeln können wie von anderen empfohlen überprüft werden – indem ein Validator-Objekt erstellt und manuell aufgerufen wird:

Validator validator = Validation.buildDefaultValidatorFactory().getValidator()
Set violations = validator.validate(contact);
assertFalse(violations.isEmpty());

Damit sollten Sie alle möglichen Fälle prüfen – es könnte Dutzende davon geben (und in diesem Fall dort sollte seien Dutzende von ihnen).

Die Validierung wird durch die Frameworks ausgelöst

In Ihrem Fall überprüfen Sie es mit Hibernate, daher sollte es einen Test geben, der es initialisiert und einige Hibernate-Operationen auslöst. Beachten Sie, dass Sie hierfür nur überprüfen müssen eine Fehlerregel für ein einzelnes Feld – das wird reichen. Sie müssen nicht alle Regeln erneut überprüfen. Beispiel könnte sein:

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

Achtung: Auslösen nicht vergessen flush(). Wenn Sie mit UUIDs oder Sequenzen als ID-Generierungsstrategie arbeiten, wird INSERT nicht geleert, wenn Sie save() – wird auf später verschoben.

Dies alles ist ein Teil davon, wie man eine Testpyramide baut – finden Sie Weitere Details hier.

  • Vielen Dank für die UUID-Informationen, das war neu für mich und hat viel Zeit gespart!

    – C-Otto

    8. November 2017 um 15:39 Uhr

  • Sir, danke für die Antwort, die mir geholfen hat. Können Sie sich meine Frage ansehen? stackoverflow.com/q/64572296/9398992

    Benutzer9398992

    28. Oktober 2020 um 14:51 Uhr

1229210cookie-checkWie teste ich Validierungsannotationen einer Klasse mit JUnit?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy