- Create a constraint annotation
- Create a constraint validator
I’m going back to yesterday’s contrived scenario of a Birmingham postal code validator for our Address object. As the code below demonstrates, all we want to do is to apply an @BirminghamPostCode to the post code field of following Address bean, together with a couple of built-in constraints: @NotEmpty and @Size.
public class Address {
@NotEmpty
@Size(min = 2, max = 25)
private String street;
private String town;
private String country;
@BirminghamPostCode(message = "This should be a Birmingham post code")
private String postCode;
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getTown() {
return town;
}
public void setTown(String town) {
this.town = town;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getPostCode() {
return postCode;
}
public void setPostCode(String post_code) {
this.postCode = post_code;
}
}
Creating the constraint annotation seems to be a matter of writing some boiler plate code and filling in the blanks. This is the Birmingham post code annotation:
@Target({ ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = BirminghamPostCodeValidator.class)
public @interface BirminghamPostCode {
String message() default "{birmingham.postcode.constraint}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
The annotation interface has three mandatory attributes: message, groups and payload, which I’m going to talk about later, all except the message string as it simply holds the default error message.
The second step is a matter of writing a class that implements the ConstraintValidator<A,T> interface, a generic class where the first argument A is the constraint annotation and the second, T is the type of object being validated; hence the method signature in this case will be:
public class BirminghamPostCodeValidator implements
ConstraintValidator<BirminghamPostCode, String>
The validator code has been updated yet again for this example. The Birmingham post code check now checks the format of the String using a regular expression (b|B)[0-9]{1,2} [0-9]{1}[a-zA-Z]{2} and yes, I know that the built-in validators come complete with a regular expression constraint, but I wanted to demonstrate the use of the
@Override
public void initialize(BirminghamPostCode constraintAnnotation)
...method that can be used to initialise your constraint validator. For the complete validator take a look at the code below:
public class BirminghamPostCodeValidator implements
ConstraintValidator<BirminghamPostCode, String> {
private static Pattern postCodePattern;
/**
* Implementing this method is optional and is usually blank in example code. Use it to
* setup your constraint validator. In this case, I've created a Pattern object to test the
* post code.
*
* @see javax.validation.ConstraintValidator#initialize(java.lang.annotation.Annotation)
*/
@Override
public void initialize(BirminghamPostCode constraintAnnotation) {
if (postCodePattern == null) {
postCodePattern = Pattern.compile("(b|B)[0-9]{1,2} [0-9]{1}[a-zA-Z]{2}");
}
}
/**
* Use this method to test the constraint.
*
* @see javax.validation.ConstraintValidator#isValid(java.lang.Object,
* javax.validation.ConstraintValidatorContext)
*/
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
boolean result = false;
if (isNotNull(value)) {
result = matchPostCode(value);
}
return result;
}
private static boolean isNotNull(Object obj) {
return obj != null;
}
private boolean matchPostCode(String value) {
Matcher matcher = postCodePattern.matcher(value);
return matcher.matches();
}
}
Having got three of the four bits of code required to demonstrate custom constraints, all that's left is to write a few quick unit tests.
public class PostCodeTest {
/**
* The validator to be used for object validation. Will be retrieved once for all test
* methods.
*/
private static Validator validator;
private Address address;
/**
* Retrieves the validator instance.
*/
@BeforeClass
public static void setUpClass() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
@Before
public void setUp() {
address = new Address();
address.setStreet("Gold Avenue");
}
/**
* One constraint violation owing to postcode being null
*/
@Test
public void postCodeIsNull() {
Set<ConstraintViolation<Address>> constraintViolations = validator.validate(address);
assertEquals(1, constraintViolations.size());
printViolations(constraintViolations);
}
private void printViolations(Set<ConstraintViolation<Address>> constraintViolations) {
for (ConstraintViolation<Address> violation : constraintViolations) {
String invalidValue = (String) violation.getInvalidValue();
String message = violation.getMessage();
System.out.println("Found constraint violation. Value: " + invalidValue
+ " Message: " + message);
}
}
/**
* One constraint violation owing to postcode being invalid
*/
@Test
public void postCodeIsInvalid() {
address.setPostCode("CA23 3ER");
Set<ConstraintViolation<Address>> constraintViolations = validator.validate(address);
assertEquals(1, constraintViolations.size());
printViolations(constraintViolations);
}
/**
* Valid post code, so no violations.
*/
@Test
public void postCodeIsValid() {
address.setPostCode("B15 1NP");
Set<ConstraintViolation<Address>> constraintViolations = validator.validate(address);
assertEquals(0, constraintViolations.size());
}
}
As I mentioned above, the Hibernate validator does not require any configuration. The JUnit’s @BeginClass method merely calls Validation.buildDefaultValidatorFactory() to get hold of all the constraint classes on the path. After building and creating a validator, the final step is to call:
Set<ConstraintViolation<Address>> constraintViolations = validator.validate(address);
If the instance under test passes validation, the constraintViolations is an empty set.
3 comments:
Roger, nice post.
Ross
Hi,
nice article.
I have my own custom annotation validation implementation.
But my implementation depends on service that it will be injected at moment of runtime.
If I follow your approach, problem occurs at the following line:
Set> constraintValidations = validator.validate(myObject);
IMHO, problem lies in fact that my validator expects to have that service.
I would like to test it without involving context files. I do not want to test it like integration test but just as unit test.
I have experience with Mockito, so some kind of that approach would suffice and should work? Do you have any idea how this can be achieved?
Thank You
I often use Mockito and combine it with Spring's ReflectionTestUtils.setField(...) method to inject mock objects in to Spring beans without the need to use the context files.
Post a comment