93

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

}
Jack
  • 6,430
  • 27
  • 80
  • 151
  • 1
    You could make use of [Hibernate Validator](http://hibernate.org/validator/documentation/getting-started/). – mkobit Mar 16 '15 at 05:34

11 Answers11

146

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>
eis
  • 51,991
  • 13
  • 150
  • 199
  • 7
    If one uses Spring, then Validator can be injected, like that: `@Autowired private Validator validator;` – Stanislav Karakhanov Nov 28 '18 at 23:42
  • 1
    @StanislavKarakhanov yes, like expanded in [this answer](https://stackoverflow.com/a/29070299/365237). However, if one is not already using Spring in this test, the Spring dependency is not needed. – eis Nov 29 '18 at 06:30
  • 1
    With @Autowired of validator you need to start Spring context. With ValidatorFactory do not. – Kirill Ch May 22 '19 at 21:55
  • this way only first level of fields will be validated. for example nested fields will not checked for validation. Is there any method to check nested fields for validations? – Homayoun Behzadian Sep 16 '19 at 12:01
  • 1
    @HomayounBehzadian if nested fields are not checked, you have annotated them incorrectly. The the same test works for nested as well. – eis Sep 16 '19 at 17:09
  • 1
    needs a @Valid annotation on each of fields – Homayoun Behzadian Sep 17 '19 at 05:39
  • @HomayounBehzadian yes, naturally. the same is with objects themselves, they need to be annotated with `@Valid` to be validated, unless validator is called explicitly. this is not the fault of the test, same is with the real use case as well - so test is exposing that you have annotated them incorrectly. – eis Sep 17 '19 at 17:16
  • 1
    @KirillCh is right. If you inject it you use the spring contect which makes it an integration not an unit test. +1 for this because using sping just for validating fields tooks way to long for execution and it is not a unit test – FishingIsLife Jun 05 '20 at 11:15
  • Depending on the entire setup, asserting the Set> to be empty might be a rather weak test. In more complex validations you might get unintended violations passing this test. It would be nice to assert that the expected violation is actually part of violations. – Simeon Mar 03 '21 at 08:24
20

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'

Oozeerally
  • 842
  • 12
  • 24
  • 2
    wouldn't you need a validator implementation also in the classpath, and not just the api? – eis Apr 16 '21 at 18:05
8

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("jack@yahoo.com", true);
}

@Test
public void testNoUpperCase() throws NoSuchFieldException {
    emailRegex("Jack@yahoo.com", false);
}
Paulo Merson
  • 13,270
  • 8
  • 79
  • 72
BevynQ
  • 8,089
  • 4
  • 25
  • 37
  • the regex should be correct coz Ive found it on Oracle website, but your comment made me think if it really works when the code is running. – Jack Mar 16 '15 at 05:57
  • 1
    @Jack the regex does not appear to allow upper case characters – BevynQ Mar 16 '15 at 06:06
5

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(""));
}
LazR
  • 631
  • 8
  • 18
4

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);
    }
}
davidxxx
  • 125,838
  • 23
  • 214
  • 215
  • I think this works because NotNull annotation has an inpact on the database itself. Database fields will become not null in database design. In my case my entity has a javax.validation.constraints.Pattern Annotation on a field which does not cause a ConstraintViolationException in a DataJpaTest. – DCO Jan 20 '22 at 13:20
3
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

ugurkocak1980
  • 143
  • 1
  • 13
2

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.

Stanislav Bashkyrtsev
  • 14,470
  • 7
  • 42
  • 45
  • 1
    Thank you for the UUID information, this was new to me and saved a lot of time! – C-Otto Nov 08 '17 at 15:39
  • sir thanks for answer it helped me, can you have a look at my question https://stackoverflow.com/q/64572296/9398992 –  Oct 28 '20 at 14:51
0

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>
chengpohi
  • 14,064
  • 1
  • 24
  • 42
  • 1
    this depends on Spring - OP makes no such claim, and adding Spring just for this is not something I'd recommend. – eis Jan 16 '17 at 07:40
0

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>
isilona
  • 101
  • 1
  • 6
0

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

    }

}
Archimedes Trajano
  • 35,625
  • 19
  • 175
  • 265
-1

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.

vikas
  • 1,535
  • 1
  • 13
  • 22
  • I read that thanks, there should be a way to test them though. Why does it pass the test even with wrong values? – Jack Mar 16 '15 at 05:41