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 s
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.
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(""));
}
There are 2 things that you need to check:
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).
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.
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);
}
}
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>
I found 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'