Jack
Jack

Reputation: 6600

How to test validation annotations of a class using JUnit?

I need to test the validation annotations but it looks like they do not work. I am not sure if the JUnit is also correct. Currently, the test will be passed but as you can see the specified email address is wrong.

JUnit

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

Class to be tested

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

}

Upvotes: 103

Views: 163603

Answers (11)

Archimedes Trajano
Archimedes Trajano

Reputation: 41230

For those with Spring Boot with Spring-Data-JPA you just need to autowire the validator

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import javax.validation.Validator;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest
class ValidatorTest {

    @Autowired
    private Validator validator;

    @Test
    void ensureValidatorIsLoaded() {

        assertThat(validator).isNotNull();
    }

    @Test
    void failValidate() {

        final var violations = validator.validate(new StartRequest());
        assertThat(violations).isNotEmpty();

    }

    @Test
    void passValidate() {

        final var startRequest = StartRequest.builder()
            .contentType("foo/bar")
            .contentMd5Hash("abcdef1234567890abcdef1234567890")
            .category("Pc")
            .contentLength(55)
            .siteId("ca1")
            .desiredExpiration(55)
            .build();
        final var violations = validator.validate(startRequest);
        assertThat(violations).isEmpty();

    }

}

Upvotes: 0

eezkren
eezkren

Reputation: 161

If you try using new versions of the validator but land on that thread (like me), you will start getting tons of wired exceptions. So should have in mind that to do test with Hibernate 7+

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>7.0.2.Final</version>
    <scope>test</scope>
</dependency>

should be sure that you are NOT using

<dependency>
    <groupId>javax.validation</groupId>
     <artifactId>validation-api</artifactId>
     <version>2.0.1.Final</version>
</dependency>

but switched to

<dependency>
    <groupId>jakarta.validation</groupId>
    <artifactId>jakarta.validation-api</artifactId>
    <version>3.0.1</version>
</dependency>

and have

<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>jakarta.el</artifactId>
    <version>4.0.2</version>
    <scope>test</scope>
</dependency>

Upvotes: 0

Oozeerally
Oozeerally

Reputation: 932

A simple way to test validation annotations using javax:

Declare the Validator at Class level:

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

Then in your test simply call it on the object you require validation on, with what exception you are validating:

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

Then simply assert the number of expected violations:

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

You will need to add this to your dependencies (gradle):

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

Upvotes: 25

Stanislav Bashkyrtsev
Stanislav Bashkyrtsev

Reputation: 15308

There are 2 things that you need to check:

The validation rules are configured correctly

The validation rules can be checked the way others advise - by creating a validator object and invoking it manually:

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

With this you should check all the possible cases - there could be dozens of them (and in this case there should be dozens of them).

The validation is triggered by the frameworks

In your case you check it with Hibernate, therefore there should be a test that initializes it and triggers some Hibernate operations. Note that for this you need to check only one failing rule for one single field - this will be enough. You don't need to check all the rules from again. Example could be:

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

NB: don't forget to trigger flush(). If you work with UUIDs or sequences as an ID generation strategy, then INSERT is not going to be flushed when you save() - it's going to be postponed until later.

This all is a part of how to build a Test Pyramid - you can find more details here.

Upvotes: 2

ugurkocak1980
ugurkocak1980

Reputation: 152

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

Strictly speaking it is not a unit test, rather an Integration Test. In Unit Test you would like to test the validator logic only, without any dependencies to the SPI.

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

Upvotes: 3

BevynQ
BevynQ

Reputation: 8259

The annotations do not do anything by themselves, you need to use a Validator to process the object.

Your test needs to run some code like this

    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

However, the difficulty you face here are these are all interfaces, you will need implementations to be able to test. You could implement it yourself or find one to use.

However the question you want to ask yourself is what are you trying to test? That the hibernate validator works the way it should? or that your regex is correct?

If this was me I would assume that the Validator works(ie someone else tested that) and focus on the regex. Which would involve a bit of reflection

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

}

then you can define your testMethods which are actual unit tests

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

Upvotes: 8

LazR
LazR

Reputation: 751

First thanks @Eis for the answer, it helped me. It's a good way to fail the test, but I wanted a bit more "life-like" behaviour. At runtime an exception would be thrown so I came up with this:

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

Have an entity with validation:

@Data
public class MyEntity {

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

}

In a test you can pass an instance with invalid attributes and expect an 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(""));
}

Upvotes: 5

eis
eis

Reputation: 53462

The other answer saying that "the annotations do not do anything by themselves, you need to use a Validator to process the object" is correct, however, the answer lacks working instructions on how to do it using a Validator instance, which for me was what I really wanted.

Hibernate-validator is the reference implementation of such a validator. You can use it quite cleanly like this:

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

This assumes you have validator implementation and junit as dependencies.

Example of dependencies using 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>

Upvotes: 156

davidxxx
davidxxx

Reputation: 131316

Here my way to unit test my objects with fields annotated with some javax.validation.constraints constraints.
I will give an example with Java 8, JPA entity, Spring Boot and JUnit 5 but the overall idea is the same whatever the context and the frameworks :
We have a nominal scenario where all fields are correctly valued and generally multiple error scenarios where one or more fields are not correctly valued.

Testing field validation is not a particularly hard thing.
But as we have many fields to validate, the tests may become more complex, we can forget some cases, introducing side effects in tests between two cases to validate or simply introduce duplication.
I will give my mind about how to avoid that.

In the OP code, we will suppose that the 3 fields have a NotNull constraint. I think that under 3 distinct constraints, the pattern and its value are less visible.

I wrote first a unit test for the nominal scenario :

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

I extract the code to create a valid contact into a method as it will be helpful for no nominal cases :

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

Now I write a @parameterizedTest with as fixture source a @MethodSource method :

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

To compile/run @parameterizedTest, think of adding the required dependency that is not included in the junit-jupiter-api dependency :

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

In the fixture method to create invalid contacts, the idea is simple. For each case, I create a new valid contact object and I set incorrectly only the field to validate concerned to.
In this way, I ensure that no side effect between cases are present and that each case provokes itself the expected validation exception as without the field set the valid contact was successful persisted.

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

Here is the full test code :

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

Upvotes: 4

chengpohi
chengpohi

Reputation: 14217

such as:

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

and you also need add bean autowired in your context.xml, such as:

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

Upvotes: 0

vikas
vikas

Reputation: 1558

I think validations would work after calling predefined methods which is usually done by the containers mostly not immediately after calling setters of the object. From the documentation link you shared:

> By default, the Persistence provider will automatically perform validation on entities with persistent fields or properties annotated with Bean Validation constraints immediately after the PrePersist, PreUpdate, and PreRemove lifecycle events.

Upvotes: -1

Related Questions