Why isn't my custom bean validation message being interpolated? - java

I can't seem to get a custom validation message to work. First I tried it with a custom validator, but it didn't work there, so following this example, I tried using a built-in constraint with custom message key, but no luck.
Here's my JUnit4 test case for the problem:
public class PatternMessageTest {
private static Validator validator;
#BeforeClass
public static void setUp() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
#Test
public void testIsValid_invalid() {
StringHolder stringHolder = new StringHolder();
stringHolder.setSomeString("not digits");
Set<ConstraintViolation<StringHolder>> constraintViolations = validator.validate(stringHolder);
assertEquals(1, constraintViolations.size());
assertEquals(
"Some validation message",
constraintViolations.iterator().next().getMessage());
}
private class StringHolder {
#Valid
#Pattern(regexp="\\d+", message="{mymessagekey}")
private String _someString;
// _someString getter, setter
}
}
Here is the contents of ValidationMessages.properties, which is in the root of my test directory:
mymessagekey=Some validation message
The test output is:
org.junit.ComparisonFailure: expected:<[Some validation message]> but was:<[{mymessagekey}]>
So the message key is apparently not being located. What am I doing wrong?
Relevant classpath:
validation-api-1.0.0.GA.jar
hibernate-validator-4.1.0.Final.jar
hibernate-validator-annotation-processor-4.1.0.Final.jar
Before getting down to such a simple test case, I was adding validation to a Spring MVC app, and the behavior is the same: I keep getting the key with surrounding braces as the message returned by validation.

Related

In JSR 303 bean validation unit test, how to check which constraints are violated

I'm trying to write a jUnit test for a bean validation.
I read How to test validation annotations of a class using JUnit?
and wrote a test code like as below.
My environment:
Sprint Boot 2.2.6
Java11
AssertJ 3.15.0
Target Bean class:
public class Customer {
#NotEmpty
private String name;
#Min(18)
private int age;
// getter and setter
}
JUnit test code:
public class CustomerValidationTest {
private Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
#Test
public void test() {
Customer customer = new Customer(null, 18);
Set<ConstraintViolation<Customer>> violations = validator.validate(customer);
assertThat(violations.size()).isEqualTo(1); // check violations count
// check which constraints are violated by the message of the violation
assertThat(violations).extracting("message").containsOnly("must not be empty");
}
}
I'd like to check which constraints are violated. For now, I check the message of violations.
Is there better way?
In your small test setup you might be able to oversee if exactly and only one violation occurs.
assertThat(violations.size()).isEqualTo(1);
and
.containsOnly("must not be empty")
However in a larger setup that might not be the case. What you actually want to do is asserting your expected violation to be present.
With the Testframework junit-jupiter-api:5.6.2 I did my test like this:
public class CustomerValidationTest {
private static Validator validator;
private static ValidatorFactory factory;
#org.junit.jupiter.api.BeforeEach
void setUp() {
Locale.setDefault(Locale.ENGLISH); //expecting english error messages
factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
#org.junit.jupiter.api.AfterEach
void tearDown() {
factory.close();
}
#org.junit.jupiter.api.Test
public void testContainsEmptyNameViolation() {
Customer customer = new Customer(null, 18);
//perform validation
Set<ConstraintViolation<Customer>> constraintViolations = validator.validate(customer);
boolean hasExpectedPropertyPath = constraintViolations.stream()
.map(ConstraintViolation::getPropertyPath)
.map(Path::toString)
.anyMatch("name"::equals);
boolean hasExpectedViolationMessage = constraintViolations.stream()
.map(ConstraintViolation::getMessage)
.anyMatch("must not be empty"::equals);
assertAll(
() -> assertFalse(constraintViolations.isEmpty()),
() -> assertTrue(hasExpectedPropertyPath),
() -> assertTrue(hasExpectedViolationMessage)
);
Even though you asked for AssertJ, I hope that this might still be of help to you.
This tutorial here shows in section 7. 'Testing .. Validations ..' a nice way of assuming that the expected violation is part of the Set.
Depending on your testing Framework this might be a strategy to follow.
#Test public void validatingObject() {
Car car = new Car();
Set<ConstraintViolation> violations = validator.validate(car);
assertThat(violations.size()).isEqualTo(1);
assertThat(violations)
.anyMatch(havingPropertyPath("customerPropertyPathForCarViolation")
.and(havingMessage("message of desired violation"))); }

Hibernate Validator : restrict validation to given constraints

I want to perform the validation of my entities in two steps. While I use a defaultValidatorFactory to validate all the fields of my entities before persisting to the database, I would like to perform a partial validation of my entities at a earlier step. But I cannot find a way to configure my validator (or validatorFactory).
Let's say I have the following class:
public class Car {
#NotNull
private String manufacturer;
#AssertTrue
private boolean isRegistered;
public Car(String manufacturer, boolean isRegistered) {
super();
this.manufacturer = manufacturer;
this.isRegistered = isRegistered;
}
}
When I do the full validation of my entity, I use the given code:
Validator validator = validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<Car>> errors = validator.validate(car);
This works fine and validate both annotations NotNull and AssertTrue.
Now, I want to perform an partial validation. I mean by partial validation, I want to only validate, for example, NotNull constraints and ignore other annotations.
Is there a way to get a Validator or ValidatorFactory which uses a custom restricted list of validators?
You can find a lot of things to create your own constraint/constraint validator. In my case, I want to validate only some constraints.
Maybe I can create a custom ConstraintValidatorFactory and inject it in the Validation context? I found that we can reconfigure the context of the factory with the following code, but I don't know how to deal with it.
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
validatorFactory.usingContext().constraintValidatorFactory(myCustomFactory);
For the moment, I'm lost. Someone has already done something like that? Do you have any idea how I can do this? Thanks for your time.
I'm using Java 8 and Hibernate Validator 6.0.14.
As Slaw write - use groups.
An Example
package jpatest.jpatest;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.constraints.NotNull;
public class TestApp {
/** A validation group marker */
public interface ValidationGroup1 {};
/** The bean */
public static class Bean {
// Validate for group ValidationGroup1
#NotNull(groups = ValidationGroup1.class)
private String s;
}
public static void main(String[] args) {
Bean b = new Bean();
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
// Validation without the validation group => No ConstraintViolation
Set<ConstraintViolation<Bean>> errors1 = validator.validate(b);
assert errors1.isEmpty() : "No ConstraintViolation expected";
// Validation with the validation group => 1 ConstraintViolation
Set<ConstraintViolation<Bean>> errors2 = validator.validate(b, ValidationGroup1.class);
assert errors2.size() == 1 : "1 ConstraintViolation expected";
}
}

How to validate a DTO only for a specific constraint and ignore the other?

Imagine a case we have such dto
#CheckUserDetailsNotNull
#CheckUserMobileValid
#CheckUserEmailValid
public class UserDto {}
and, ofcourse, for each of these annotations there are dedicated constraint validators, for example for #CheckUserMobileValid there is UserMobileValidator
So, I'm gonna write a unit test for the UserMobileValidator but in scope of the test I wanna check only #CheckUserMobileValid without trigger the other validators. Here is the simple test:
public class UserMobileValidatorTest {
private Validator validator;
#Before
public void setup() {
validator = Validation.buildDefaultValidatorFactory().getValidator();
}
#Test
public void mobileShouldBeValid() {
UserDto userDto = new UserDto();
userDto.setMobile("062233442234");
Set<ConstraintViolation<UserDto>> constraintViolations = validator.validate(UserDto);
Assert.assertEquals("Expected validation error not found", 1, constraintViolations.size());
}
It works just fine but the all validators will be triggered, Is there a way to trigger only UserMobileValidator?

Manually call Spring Annotation Validation

I'm doing a lot of our validation with Hibernate and Spring Annotations like so:
public class Account {
#NotEmpty(groups = {Step1.class, Step2.class})
private String name;
#NotNull(groups = {Step2.class})
private Long accountNumber;
public interface Step1{}
public interface Step2{}
}
And then in the controller it's called in the arguments:
public String saveAccount(#ModelAttribute #Validated({Account.Step1.class}) Account account, BindingResult result) {
//some more code and stuff here
return "";
}
But I would like to decide the group used based on some logic in the controller method. Is there a way to call validation manually? Something like result = account.validate(Account.Step1.class)?
I am aware of creating your own Validator class, but that's something I want to avoid, I would prefer to just use the annotations on the class variables themselves.
Spring provides LocalValidatorFactoryBean, which implements the Spring SmartValidator interface as well as the Java Bean Validation Validator interface.
// org.springframework.validation.SmartValidator - implemented by LocalValidatorFactoryBean
#Autowired
SmartValidator validator;
public String saveAccount(#ModelAttribute Account account, BindingResult result) {
// ... custom logic
validator.validate(account, result, Account.Step1.class);
if (result.hasErrors()) {
// ... on binding or validation errors
} else {
// ... on no errors
}
return "";
}
Here is a code sample from JSR 303 spec
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Driver driver = new Driver();
driver.setAge(16);
Car porsche = new Car();
driver.setCar(porsche);
Set<ConstraintViolation<Driver>> violations = validator.validate( driver );
So yes, you can just get a validator instance from the validator factory and run the validation yourself, then check to see if there are violations or not. You can see in the javadoc for Validator that it will also accept an array of groups to validate against.
Obviously this uses JSR-303 validation directly instead of going through Spring validation, but I believe spring validation annotations will use JSR-303 if it's found in the classpath
If you have everything correctly configured, you can do this:
import javax.validation.Validator;
#Autowired
Validator validator;
Then you can use it to validate you object:
var errors = validator.validate(obj);
This link gives pretty good examples of using validations in Spring apps.
https://reflectoring.io/bean-validation-with-spring-boot/
I have found an example to run the validation programmitically in this article.
class MyValidatingService {
void validatePerson(Person person) {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<Person>> violations = validator.validate(person);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
}
}
It throws 500 status, so it is recommended to handle it with custom exception handler.
#ControllerAdvice(annotations = RestController.class)
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<CustomErrorResponse> constraintViolationException(HttpServletResponse response, Exception ex) throws IOException {
CustomErrorResponse errorResponse = new CustomErrorResponse();
errorResponse.setTimestamp(LocalDateTime.now());
errorResponse.setStatus(HttpStatus.BAD_REQUEST.value());
errorResponse.setError(HttpStatus.BAD_REQUEST.getReasonPhrase());
errorResponse.setMessage(ex.getMessage());
return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
}
}
Second example is from https://www.mkyong.com/spring-boot/spring-rest-error-handling-example/
Update:
Using validation is persistence layer is not recommended:
https://twitter.com/odrotbohm/status/1055015506326052865
Adding to answered by #digitaljoel, you can throw the ConstraintViolationException once you got the set of violations.
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<NotionalProviderPaymentDTO>> violations = validator.validate( notionalProviderPaymentDTO );
if(!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
You can create your own exception mapper which will handle ConstraintViolationException and send the errors messages to the client.
And also:
#Autowired
#Qualifier("mvcValidator")
Validator validator;
...
violations = validator.validate(account);
import javax.validation.Validator;
import javax.validation.ConstraintViolation;
public class{
#Autowired
private Validator validator;
.
.
public void validateEmployee(Employee employee){
Set<ConstraintViolation<Employee>> violations = validator.validate(employee);
if(!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
}
}
Here, 'Employee' is a pojo class and 'employee' is it's object

Generating a error code per property in Hibernate Validator

I am looking at using Hibernate Validator for a requirement of mine. I want to validate a JavaBean where properties may have multiple validation checks. For example:
class MyValidationBean
{
#NotNull
#Length( min = 5, max = 10 )
private String myProperty;
}
But if this property fails validation I want a specific error code to be associated with the ConstraintViolation, regardless of whether it failed because of #Required or #Length, although I would like to preserve the error message.
class MyValidationBean
{
#NotNull
#Length( min = 5, max = 10 )
#ErrorCode( "1234" )
private String myProperty;
}
Something like the above would be good but it doesn't have to be structured exactly like that. I can't see a way to do this with Hibernate Validator. Is it possible?
You could create a custom annotation to get the behaviour you are looking for and then on validating and using refelection you could extract the value of the annotation. Something like the following:
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface ErrorCode {
String value();
}
In your bean:
#NotNull
#Length( min = 5, max = 10 )
#ErrorCode("1234")
public String myProperty;
On validating your bean:
Set<ConstraintViolation<MyValidationBean>> constraintViolations = validator.validate(myValidationBean);
for (ConstraintViolation<MyValidationBean>cv: constraintViolations) {
ErrorCode errorCode = cv.getRootBeanClass().getField(cv.getPropertyPath().toString()).getAnnotation(ErrorCode.class);
System.out.println("ErrorCode:" + errorCode.value());
}
Having said that I probably would question the requirements for wanting error codes for these types of messages.
From the section 4.2. ConstraintViolation of the specification:
The getMessageTemplate method returns the non-interpolated error message (usually the message attribute on the constraint declaration). Frameworks can use this as an error code key.
I think this is your best option.
What I would try to do is isolate this behavior on the DAO Layer of the application.
Using your example we would have:
public class MyValidationBeanDAO {
public void persist(MyValidationBean element) throws DAOException{
Set<ConstraintViolation> constraintViolations = validator.validate(element);
if(!constraintViolations.isEmpty()){
throw new DAOException("1234", contraintViolations);
}
// it's ok, just persist it
session.saveOrUpdate(element);
}
}
And the following exception class:
public class DAOException extends Exception {
private final String errorCode;
private final Set<ConstraintViolation> constraintViolations;
public DAOException(String errorCode, Set<ConstraintViolation> constraintViolations){
super(String.format("Errorcode %s", errorCode));
this.errorCode = errorCode;
this.constraintViolations = constraintViolations;
}
// getters for properties here
}
You could add some annotation information based on what property has not validated from here, but always doing this on the DAO method.
I hope this helped.

Categories