Spring Boot: Why is an autowired object in Custom Validator class is null? - spring

The following code yields null on the teamResource auto-wired object inside isValid() function:
#Component //I am not sure it is required he
public class TeamIdValidator implements ConstraintValidator<TeamIdConstraint, Integer> {
#Autowired private TeamResource teamResource;
public TeamIdValidator() {
}
#Override
public void initialize(TeamIdConstraint teamIdConstraint) {
// this.teamIdConstraint = teamIdConstraint;
}
#Override
public boolean isValid(Integer teamId, ConstraintValidatorContext cxt) {
int numOfAvailableTeams = teamResource.retrieveAllTeams().size(); //teamResource is null :(
return teamId < 0 || teamId >= numOfAvailableTeams;
}
}
TeamResource class:
#RestController
#CrossOrigin
public class TeamResource {
#Autowired private TeamRepository teamRepository;
public TeamResource() {
}
...
//mapping methods
}
And if it relevant...
#Documented
#Constraint(validatedBy = TeamIdValidator.class)
#Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
#Retention(RetentionPolicy.RUNTIME)
public #interface TeamIdConstraint {
String message() default "Invalid team id!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Why on earth teamResource is null?
It is not being explicitly initialized with new anywhere else.

Already mentioned in the comments but declare a a bean like this
#Configuration
public class ValidationConfig{
#Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor(Validator validator) {
MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(validator);
return methodValidationPostProcessor;
}
}
Remove all the #autowired, prefer constructor injection. (check the internet why).
And then, it makes no sense to inject a RestContoller to validator.

Related

How to validate a single value but not annotated pojo with specific ConstraintValidator in spring boot

My purpose is to use a specific ConstraintValidator in 2 scenarios below
use annotations on POJO to validate the whole object (the popular way)
validate a single value with specific validator's isValid function (for some configurable dynamic validation request)
The validator must support services injection, so I must get it from spring but not create a new validator instance manually.
followed my test codes:
annotation
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = { IdNumberValidator.class })
public #interface IdNumber {
String message() default "id number is not available";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
validator
public class IdNumberValidator implements ConstraintValidator<IdNumber, String>, BeanNameAware {
#Autowired
private IUserService usrService;
private String bn;
#Override
public boolean isValid(String value, ConstraintValidatorContext context){
System.out.println("my name is:" + bn);
return usrService.getUserByAccount(value).isPresent();
}
#Override
public void setBeanName(String name) {
this.bn = name;
}
}
pojo
#Getter
#Setter
public class TestPOJO {
private long id;
#IdNumber
private String idn;
}
service
#Service
public class TestValidatorService {
#Autowired
private Validator validator;
#Autowired
private ApplicationContext context;
public void validatePojo(TestPOJO pojo){
BeanPropertyBindingResult e = new BeanPropertyBindingResult(pojo, "TestPOJO");
validator.validate(pojo, e);
if(e.hasErrors()){
for(ObjectError oe : e.getAllErrors()){
System.out.println(oe.toString());
}
}
}
public void validatePojoByDynamicValidator(TestPOJO pojo){
IdNumberValidator validator = context.getBean("com.test.IdNumberValidator", IdNumberValidator.class); // got the name via BeanNameAware but seems not working
System.out.println(validator.isValid(pojo.getIdn(), null));
}
}
In the test case for service, function "validatePojo" passed but "validatePojoByDynamicValidator" did not.
Any solution for this problem? Thanks!

Spring JPA Custom Validator Bean Inyection

Any bean inyected into CustomValidator Implementation are always null. Im using Spring Boot 2, the applciation is a REST API, i'm not using MVC.
I have tried everything I have read about this with no luck so far.
this topic here for example did not work for me
Should I validate otherwise?, I've been stuck with this for 2 days already.
This is my Config class:
#Configuration
public class Beans {
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:idiomas/idioma");
messageSource.setUseCodeAsDefaultMessage(true);
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
#Bean
public LocaleResolver localeResolver() {
AcceptHeaderLocaleResolver resolver = new AcceptHeaderLocaleResolver();
resolver.setDefaultLocale(new Locale("es"));
return resolver;
}
#Bean
public LocalValidatorFactoryBean getValidator() {
LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean();
validatorFactoryBean.setValidationMessageSource(messageSource());
return validatorFactoryBean;
}
#Bean
public CommonsRequestLoggingFilter logFilter() {
CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter();
filter.setIncludeQueryString(true);
filter.setIncludePayload(true);
filter.setMaxPayloadLength(10000);
filter.setIncludeHeaders(true);
filter.setAfterMessagePrefix("REQUEST: ");
return filter;
}
}
My CustomValidator looks like this:
public class AlmacenValidator implements ConstraintValidator {
#Autowired
private AlmacenService servicio;
#Autowired
private ApplicationContext contexto;
#Override
public void initialize(AlmacenValido constraintAnnotation) {
}
#Override
public boolean isValid(Almacen item, ConstraintValidatorContext context) {
//Database calls
}
JPA Entity:
#Entity
#AlmacenValido
#Table(name = "almacenes")
public class Almacen {
//Entity fields
}
The Annotation:
#Documented
#Retention(RUNTIME)
#Target({ TYPE, FIELD, ANNOTATION_TYPE, PARAMETER })
#Constraint(validatedBy = AlmacenValidator.class)
public #interface AlmacenValido {
String message() default "{validacion.almacen}";
Class[] groups() default {};
Class[] payload() default {};
}
I've created test example for you. Please check it https://github.com/alex-petrov81/stackoverflow-answers/tree/master/validation-bean-injection

Custom spring validator not being invoked

I've created a custom constraint validator that works on a list of objects. Unfortunately it doesn't seem to be getting invoked, it worked when I had a wrapper class containing the list with the annotation on the list.
This is the code that worked fine
public class wrapper {
#ValidMyObjectList
List<MyObject> myObjects;
...
}
But now I've got rid of the wrapper class and added the annotation to the parameter in the controller method.
Here's the controller
#RequestMapping(value = "", method = RequestMethod.POST)
public List<MyObject> stopCheque(
#ValidMyObjectList #RequestBody final List<MyObject> myObjects,
final HttpServletResponse httpServletResponse) {
....
}
Here's the constraint annotation
#Constraint(validatedBy = MyObjectListValidator.class)
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.FIELD, ElementType.PARAMETER })
public #interface MyObjectList {
Class<?>[] groups() default {};
String message() default "";
Class<? extends Payload>[] payload() default {};
}
And part of the validator itself
public class MyObjectListValidator implements
ConstraintValidator<MyObjectList, List<MyObject>> {
#Override
public void initialize(final MyObjectList myObjectList) {
}
#Override
public boolean isValid(final List<MyObjectList> myObjectLists, final ConstraintValidatorContext cxt) {
...
}
Would greatly appreciate any help. Thanks
Add to your Spring config class:
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
and add #Validated to controller class.

Spring's #Validated ignored custom constraint without group

I have controller with method:
void save(#Validated(BookingForm.ValidationSave.class) #RequestBody BookingForm form, Errors result) {...}
and BookingForm with a field and my custom validator:
public class BookingForm {
public interface ValidationSave {}
public interface ValidationEstimate {}
....
#Future
private LocalDateTime when;
....
}
Future.java
#Target({ ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = FutureValidator.class)
#Documented
public #interface Future {
String message() default "{javax.validation.constraints.Future.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
I've expected that validator will valid my 'when' field but it does not.
When I removed the group from #Validated it works. Any ideas?
Works fine with #NotNull and others javax validators.
Updated:
FutureValidator.java
public class FutureValidator implements ConstraintValidator<Future, Temporal> {
#Override
public void initialize(Future constraintAnnotation) {
}
#Override
public boolean isValid(Temporal value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
LocalDate ld = LocalDate.from(value);
return ld.isAfter(LocalDate.now());
}
}

Test Custom Validator with Autowired spring Service

I have a custom Hibernate Validator for my entities. One of my validators uses an Autowired Spring #Repository. The application works fine and my repository is Autowired successfully on my validator.
The problem is i can't find a way to test my validator, cause i can't inject my repository inside it.
Person.class:
#Entity
#Table(schema = "dbo", name = "Person")
#PersonNameMustBeUnique
public class Person {
#Id
#GeneratedValue
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#Column()
#NotBlank()
private String name;
//getters and setters
//...
}
PersonNameMustBeUnique.class
#Target({ TYPE, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = { PersonNameMustBeUniqueValidator.class })
#Documented
public #interface PersonNameMustBeUnique{
String message() default "";
Class<?>[] groups() default {};
Class<? extends javax.validation.Payload>[] payload() default {};
}
The validator:
public class PersonNameMustBeUniqueValidatorimplements ConstraintValidator<PersonNameMustBeUnique, Person> {
#Autowired
private PersonRepository repository;
#Override
public void initialize(PersonNameMustBeUnique constraintAnnotation) { }
#Override
public boolean isValid(Person entidade, ConstraintValidatorContext context) {
if ( entidade == null ) {
return true;
}
context.disableDefaultConstraintViolation();
boolean isValid = nameMustBeUnique(entidade, context);
return isValid;
}
private boolean nameMustBeUnique(Person entidade, ConstraintValidatorContext context) {
//CALL REPOSITORY TO CHECK IF THE NAME IS UNIQUE
//ADD errors if not unique...
}
}
And the context file has a validator bean:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
Again, it works fine, but i don't know how to test it.
My test file is:
#RunWith(MockitoJUnitRunner.class)
public class PersonTest {
Person e;
static Validator validator;
#BeforeClass
public static void setUpClass() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
#Test
public void name__must_not_be_null() {
e = new Person();
e.setName(null);
Set<ConstraintViolation<Person>> violations = validator.validate(e);
assertViolacao(violations, "name", "Name must not be null");
}
}
I was facing very similar problem: How to write pure unit test of custom validator wich has autowired configuration bean?
I could manage to solve it by following code (inspired by this answer of user abhishekrvce).
This is pure unit test of custom validator with #Autowired configuration bean, which reads the data from configuration file (not showed in code).
#Import({MyValidator.class})
#ContextConfiguration(classes = MyConfiguration.class, initializers = ConfigFileApplicationContextInitializer.class)
class MyValidatorTest {
private LocalValidatorFactoryBean validator;
#Autowired
private ConfigurableApplicationContext applicationContext;
#BeforeEach
void initialize() {
SpringConstraintValidatorFactory springConstraintValidatorFactory
= new SpringConstraintValidatorFactory(
applicationContext.getAutowireCapableBeanFactory());
validator = new LocalValidatorFactoryBean();
validator.setConstraintValidatorFactory(springConstraintValidatorFactory);
validator.setApplicationContext(applicationContext);
validator.afterPropertiesSet();
}
#Test
void isValid()
{
Set<ConstraintViolation<MyObject>> constraintViolations = validator
.validate(myObjectInstance);
assertThat(constraintViolations).hasSize(1);
}
}
U can add the following bean to your Spring Context in the test:
#RunWith(SpringRunner.class)
#Import(LocalValidatorFactoryBean.class)
public class PersonTest {
#Autowired
private Validator validator;
{
validator.validate(new Person());
}
...
}
On #BeforeClass:
#BeforeClass
public static void setUpClass() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
And in your test you need to replace the beans with your mocked bean:
myValidator.initialize(null);
BeanValidatorTestUtils.replaceValidatorInContext(validator, usuarioValidoValidator, e);
The class that do all the magic:
public class BeanValidatorTestUtils {
#SuppressWarnings({ "rawtypes", "unchecked" })
public static <A extends Annotation, E> void replaceValidatorInContext(Validator validator,
final ConstraintValidator<A, ?> validatorInstance,
E instanceToBeValidated) {
final Class<A> anotacaoDoValidador = (Class<A>)
((ParameterizedType) validatorInstance.getClass().getGenericInterfaces()[0])
.getActualTypeArguments()[0];
ValidationContextBuilder valCtxBuilder = ReflectionTestUtils.<ValidationContextBuilder>invokeMethod(validator,
"getValidationContext");
ValidationContext<E> validationContext = valCtxBuilder.forValidate(instanceToBeValidated);
ConstraintValidatorManager constraintValidatorManager = validationContext.getConstraintValidatorManager();
final ConcurrentHashMap nonSpyHashMap = new ConcurrentHashMap();
ConcurrentHashMap spyHashMap = spy(nonSpyHashMap);
doAnswer(new Answer<Object>() {
#Override public Object answer(InvocationOnMock invocation) throws Throwable {
Object key = invocation.getArguments()[0];
Object keyAnnotation = ReflectionTestUtils.getField(key, "annotation");
if (anotacaoDoValidador.isInstance(keyAnnotation)) {
return validatorInstance;
}
return nonSpyHashMap.get(key);
}
}).when(spyHashMap).get(any());
ReflectionTestUtils.setField(constraintValidatorManager, "constraintValidatorCache", spyHashMap);
}
}
We also faced the similar problem where #Autowiring was failing (not initialised) in ConstrainValidator Class. Our ConstraintValidator Implemented class was using a value which supposed to be read from the application.yml file. Below solution helped us as this is using a pure spring scope. Hope this helps, with proper SpringJunit4ClassRunner.
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.SpringConstraintValidatorFactory;
import org.springframework.web.context.WebApplicationContext;
#WebAppConfiguration
#ContextConfiguration(classes = {ApplicationConfig.class})
#RunWith(SpringJUnit4ClassRunner.class)
#TestPropertySource(properties = {
"spring.someConfigValue.InApplicationYaml=Value1",
})
public class MyTest {
#Autowired
private WebApplicationContext webApplicationContext;
LocalValidatorFactoryBean validator;
#Before
public void setup() {
SpringConstraintValidatorFactory springConstraintValidatorFactory
= new SpringConstraintValidatorFactory(webApplicationContext.getAutowireCapableBeanFactory());
validator = new LocalValidatorFactoryBean();
validator.setConstraintValidatorFactory(springConstraintValidatorFactory);
validator.setApplicationContext(webApplicationContext);
validator.afterPropertiesSet();
}
#Test
public void should_have_no_violations_for_all_valid_fields() {
Set<ConstraintViolation<PojoClassWhichHaveConstraintValidationAnnotation>> violations = validator.validate(pojoClassObjectWhichHaveConstraintValidationAnnotation);
assertTrue(violations.isEmpty());
}
}
#Configuration
public class ApplicationConfig {
#Value("${spring.someConfigValue.InApplicationYaml=Value1}")
public String configValueToBeReadFromApplicationYamlFile;
}
Recently I had the same problem with my custom validator. I needed to validate a model being passed to a controller's method (method level validation). The validator invoked but the dependencies (#Autowired) could not be injected. It took me some days searching and debugging the whole process. Finally, I could make it work. I hope my experience save some time for others with the same problem. Here is my solution:
Having a jsr-303 custom validator like this:
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.FIELD,
ElementType.PARAMETER,
ElementType.TYPE,
ElementType.METHOD,
ElementType.LOCAL_VARIABLE,
ElementType.CONSTRUCTOR,
ElementType.TYPE_PARAMETER,
ElementType.TYPE_USE })
#Constraint(validatedBy = SampleValidator.class)
public #interface ValidSample {
String message() default "Default sample validation error";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class SampleValidator implements ConstraintValidator<ValidSample, SampleModel> {
#Autowired
private SampleService service;
public void initialize(ValidSample constraintAnnotation) {
//init
}
public boolean isValid(SampleModel sample, ConstraintValidatorContext context) {
service.doSomething();
return true;
}
}
You should configure spring test like this:
#ComponentScan(basePackages = { "your base packages" })
#Configurable
#EnableWebMvc
class SpringTestConfig {
#Autowired
private WebApplicationContext wac;
#Bean
public Validator validator() {
SpringConstraintValidatorFactory scvf = new SpringConstraintValidatorFactory(wac.getAutowireCapableBeanFactory());
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setConstraintValidatorFactory(scvf);
validator.setApplicationContext(wac);
validator.afterPropertiesSet();
return validator;
}
#Bean
public MethodValidationPostProcessor mvpp() {
MethodValidationPostProcessor mvpp = new MethodValidationPostProcessor();
mvpp.setValidatorFactory((ValidatorFactory) validator());
return mvpp;
}
#Bean
SampleService sampleService() {
return Mockito.mock(SampleService.class);
}
}
#WebAppConfiguration
#ContextConfiguration(classes = { SpringTestConfig.class, AnotherConfig.class })
public class ASampleSpringTest extends AbstractTestNGSpringContextTests {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#BeforeClass
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.webAppContextSetup(wac)
.build();
}
#Test
public void testSomeMethodInvokingCustomValidation(){
// test implementation
// for example:
mockMvc.perform(post("/url/mapped/to/controller")
.accept(MediaType.APPLICATION_JSON_UTF8)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(json))
.andExpect(status().isOk());
}
}
Note that, here I am using testng, but you can use JUnit 4. The whole configuration would be the same except that you would run the test with #RunWith(SpringJUnit4ClassRunner.class) and do not extend the AbstractTestNGSpringContextTests.
Now, #ValidSample can be used in places mentioned in #Target() of the custom annotation.
Attention: If you are going to use the #ValidSample annotation on method level (like validating method arguments), then you should put class level annotation #Validated on the class where its method is using your annotation, for example on a controller or on a service class.
A solution with JUnit4 and Mockito:
#Import(LocalValidatorFactoryBean.class)
#RunWith(SpringRunner.class)
public class MyCustomValidatorTest {
#Autowired
private Validator validator;
#MockBean
private PersonRepository repository;
#Test
public void name_must_not_be_null() {
// given
when(repository.findByName(any())).thenReturn(Collection.emptyList());
Person person = new Person();
person.setName(null);
// when
Set<ConstraintViolation<Person>> violations = validator.validate(person);
// then
assertViolation(violations, "name", "Name must not be null");
}
}
You can test the validator stand alone and use reflection for inject the autowired attribute.
Constraint annotation:
#Target({ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = EmailAlreadyExistsValidator.class)
public #interface EmailAlreadyExists {
String message() default "Email already exists in the database";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Validator:
public class EmailAlreadyExistsValidator implements
ConstraintValidator<EmailAlreadyExists, String> {
#Autowired
private UserRepository repository;
#Override
public void initialize(EmailAlreadyExists constraintAnnotation) {}
public boolean isValid(String email, ConstraintValidatorContext context) {
Optional<User> opUser = repository.findByEmail(email);
return (opUser.isEmpty());
}
}
Unit Test (ReflectionTestUtils do the magic):
public class EmailAlreadyExistsValidatorTest {
#Mock
private EmailAlreadyExists emailAlreadyExists;
#Mock
private ConstraintValidatorContext constraintValidatorContext;
#Mock
private UserRepository repository;
private EmailAlreadyExistsValidator validator;
#BeforeEach
public void beforeEach() {
MockitoAnnotations.openMocks(this);
validator = new EmailAlreadyExistsValidator();
validator.initialize(emailAlreadyExists);
ReflectionTestUtils.setField(validator, "repository", repository);
}
#Test
#DisplayName("Given an user with existent email then validation must fail")
public void isValid_existentPassword_mustFail() {
final String existentEmail = "testuser#test.com";
User savedUser = new User("1213443455",
"Test User",
existentEmail,
"12345",
new Date());
Optional<User> opUser = Optional.of(savedUser);
when(repository.findByEmail(anyString())).thenReturn(opUser);
assertFalse(validator.isValid(existentEmail,constraintValidatorContext));
}
}
It might be a bit late but I faced the same issue lately so I'll post how I solved the problem, as this could help other people.
The problem is basically that Hibernate's standard Validator implementation that you get by calling Validation.buildDefaultValidatorFactory().getValidator() does not know anything about Spring's application context so it cannot inject dependencies in your custom constraint validators.
In a Spring application the implementation of both the Validator and the ValidatorFactory interface is the class LocalValidatorFactoryBean, which can delegate to the ApplicationContext to instantiate constraint validators with dependencies injected.
What you need to do is
Instantiate your constraint validators with their (mocked, I presume) dependencies
Create your own ValidatorFactory that holds all the constraint validators from bulletpoint 1
Instantiate your Validator from such factory
This is the custom validator factory
public class CustomLocalValidatorFactoryBean extends LocalValidatorFactoryBean {
private final List<ConstraintValidator<?, ?>> customConstraintValidators;
public CustomLocalValidatorFactoryBean(List<ConstraintValidator<?, ?>> customConstraintValidators) {
this.customConstraintValidators = customConstraintValidators;
setProviderClass(HibernateValidator.class);
afterPropertiesSet();
}
#Override
protected void postProcessConfiguration(Configuration<?> configuration) {
super.postProcessConfiguration(configuration);
ConstraintValidatorFactory defaultConstraintValidatorFactory =
configuration.getDefaultConstraintValidatorFactory();
configuration.constraintValidatorFactory(
new ConstraintValidatorFactory() {
#Override
public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
for (ConstraintValidator<?, ?> constraintValidator : customConstraintValidators) {
if (key.equals(constraintValidator.getClass())) //noinspection unchecked
return (T) constraintValidator;
}
return defaultConstraintValidatorFactory.getInstance(key);
}
#Override
public void releaseInstance(ConstraintValidator<?, ?> instance) {
defaultConstraintValidatorFactory
.releaseInstance(instance);
}
}
);
}
}
then in your test class you'd just do something like this:
class MyTestSuite {
private final PersonRepository mockPersonRepository = Mockito.mock(PersonRepository.class);
private final List<ConstraintValidator<?,?>> customConstraintValidators =
Collections.singletonList(new PersonNameMustBeUniqueValidator(mockPersonRepository));
private final ValidatorFactory customValidatorFactory =
new CustomLocalValidatorFactoryBean(customConstraintValidators);
private final Validator validator = customValidatorFactory.getValidator();
#Test
void myTestCase() {
// mock the dependency: Mockito.when(mockPersonRepository...)
Person p = new Person();
//setters omitted
Set<ConstraintViolation<?>> violations = validator.validate(p);
//assertions on the set of constraint violations
}
}
Hope that helps. You can check out this post of mine for more details: https://codemadeclear.com/index.php/2021/01/26/how-to-mock-dependencies-when-unit-testing-custom-validators/
I've implemented by overriding default Hibernate ConstraintValidatorFactory in my UnitTests
LocalValidatorFactoryBean localValidatorFactory = new LocalValidatorFactoryBean();
localValidatorFactory.setProviderClass(HibernateValidator.class);
localValidatorFactory.setConstraintValidatorFactory(new ConstraintValidatorFactoryImpl() {
#Override
public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> arg0) {
T ret = super.getInstance(arg0);
if (ret instanceof UniqueEmailValidator) {
((UniqueEmailValidator) ret).setUserService(userService);
}
return ret;
}
});
localValidatorFactory.afterPropertiesSet();
Spring Boot 2 allows to inject Bean in custom Validator without any fuss.The Spring framework automatically detects all classes which implement the ConstraintValidator interface, instantiate them, and wire all dependencies.
I had Similar problem , this is how i have implemented.
Step 1 Interface
#Documented
#Constraint(validatedBy = UniqueFieldValidator.class)
#Target({ ElementType.METHOD,ElementType.ANNOTATION_TYPE,ElementType.PARAMETER })
#Retention(RetentionPolicy.RUNTIME)
public #interface UniqueField {
String message() default "Duplicate Name";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Step 2 Validator
public class UniqueFieldValidator implements ConstraintValidator<UniqueField, Person> {
#Autowired
PersionList personRepository;
private static final Logger log = LoggerFactory.getLogger(PersonRepository.class);
#Override
public boolean isValid(Person object, ConstraintValidatorContext context) {
log.info("Validating Person for Duplicate {}",object);
return personRepository.isPresent(object);
}
}
Usage
#Component
#Validated
public class PersonService {
#Autowired
PersionList personRepository;
public void addPerson(#UniqueField Person person) {
personRepository.add(person);
}
}

Resources