Mocked Spring #Service that has #Retryable annotations on methods fails with UnfinishedVerificationException - spring

I'm using Spring Boot 1.4.0.RELEASE with spring-boot-starter-batch, spring-boot-starter-aop and spring-retry
I have a Spring Integration test that has a #Service which is mocked at runtime. I've noticed that if the #Service class contains any #Retryable annotations on its methods, then it appears to interfere with Mockito.verify(), I get a UnfinishedVerificationException. I presume this must be something to do with spring-aop? If I comment out all #Retryable annotations in the #Service then verify works ok again.
I have created a github project that demonstrates this issue.
It fails in sample.batch.MockBatchTestWithRetryVerificationFailures.batchTest() at validateMockitoUsage();
With something like:
12:05:36.554 [main] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - After test method: context [DefaultTestContext#5ec0a365 testClass = MockBatchTestWithRetryVerificationFailures, testInstance = sample.batch.MockBatchTestWithRetryVerificationFailures#5abca1e0, testMethod = batchTest#MockBatchTestWithRetryVerificationFailures, testException = org.mockito.exceptions.misusing.UnfinishedVerificationException:
Missing method call for verify(mock) here:
-> at sample.batch.service.MyRetryService$$FastClassBySpringCGLIB$$7573ce2a.invoke(<generated>)
Example of correct verification:
verify(mock).doSomething()
However I have another class (sample.batch.MockBatchTestWithNoRetryWorking.batchTest()) with a mocked #Service that doesn't have any #Retryable annotation and verify works fine.
What am I doing wrong?
In my pom.xml I have the following:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.0.RELEASE</version>
</parent>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
...
Then all the related Java Classes
#SpringBootApplication
#EnableBatchProcessing
#Configuration
#EnableRetry
public class SampleBatchApplication {
#Autowired
private JobBuilderFactory jobs;
#Autowired
private StepBuilderFactory steps;
#Autowired
private MyRetryService myRetryService;
#Autowired
private MyServiceNoRetry myServiceNoRetry;
#Bean
protected Tasklet tasklet() {
return new Tasklet() {
#Override
public RepeatStatus execute(StepContribution contribution,
ChunkContext context) {
myServiceNoRetry.process();
myRetryService.process();
return RepeatStatus.FINISHED;
}
};
}
#Bean
public Job job() throws Exception {
return this.jobs.get("job").start(step1()).build();
}
#Bean
protected Step step1() throws Exception {
return this.steps.get("step1").tasklet(tasklet()).build();
}
public static void main(String[] args) throws Exception {
// System.exit is common for Batch applications since the exit code can be used to
// drive a workflow
System.exit(SpringApplication
.exit(SpringApplication.run(SampleBatchApplication.class, args)));
}
#Bean
ResourcelessTransactionManager transactionManager() {
return new ResourcelessTransactionManager();
}
#Bean
public JobRepository getJobRepo() throws Exception {
return new MapJobRepositoryFactoryBean(transactionManager()).getObject();
}
}
#Service
public class MyRetryService {
public static final Logger LOG = LoggerFactory.getLogger(MyRetryService.class);
#Retryable(maxAttempts = 5, include = RuntimeException.class, backoff = #Backoff(delay = 100, multiplier = 2))
public boolean process() {
double random = Math.random();
LOG.info("Running process, random value {}", random);
if (random > 0.2d) {
throw new RuntimeException("Random fail time!");
}
return true;
}
}
#Service
public class MyServiceNoRetry {
public static final Logger LOG = LoggerFactory.getLogger(MyServiceNoRetry.class);
public boolean process() {
LOG.info("Running process that doesn't do retry");
return true;
}
}
#ActiveProfiles("Test")
#ContextConfiguration(classes = {SampleBatchApplication.class, MockBatchTestWithNoRetryWorking.MockedRetryService.class}, loader = AnnotationConfigContextLoader.class)
#RunWith(SpringRunner.class)
public class MockBatchTestWithNoRetryWorking {
#Autowired
MyServiceNoRetry service;
#Test
public void batchTest() {
service.process();
verify(service).process();
validateMockitoUsage();
}
public static class MockedRetryService {
#Bean
#Primary
public MyServiceNoRetry myService() {
return mock(MyServiceNoRetry.class);
}
}
}
#ActiveProfiles("Test")
#ContextConfiguration(classes = { SampleBatchApplication.class,
MockBatchTestWithRetryVerificationFailures.MockedRetryService.class },
loader = AnnotationConfigContextLoader.class)
#RunWith(SpringRunner.class)
public class MockBatchTestWithRetryVerificationFailures {
#Autowired
MyRetryService service;
#Test
public void batchTest() {
service.process();
verify(service).process();
validateMockitoUsage();
}
public static class MockedRetryService {
#Bean
#Primary
public MyRetryService myRetryService() {
return mock(MyRetryService.class);
}
}
}
EDIT: Updated question and code based on a sample project I put together to show the problem.

So after looking at a similar github issue for spring-boot
I found that there is an extra proxy getting in the way. I found a nasty hack by unwrapping the aop class by hand, makes verification work, ie:
#Test
public void batchTest() throws Exception {
service.process();
if (service instanceof Advised) {
service = (MyRetryService) ((Advised) service).getTargetSource().getTarget();
}
verify(service).process();
validateMockitoUsage();
}
Hopefully, this can be fixed similar to the above github issue. I'll raise an issue and see how far I get.
EDIT: Raised the github issue

After I've seen #Joel Pearsons answer, and especially the linked GitHub issue, I worked around this by temporarily using a static helper method that unwraps and verifies:
public static <T> T unwrapAndVerify(T mock, VerificationMode mode) {
return ((T) Mockito.verify(AopTestUtils.getTargetObject(mock), mode));
}
With this method the only difference in the test cases is the verification call. There is no overhead other than this:
unwrapAndVerify(service, times(2)).process();
instead of
verify(service, times(2)).process();
Actually, it was even possible to name the helper method like the actual Mockito method, so that you only need to replace the import, but I didn't like the subsequent confusion.
However, unwrapping shouldn't be required if #MockBean is used instead of mock() to create the mocked bean. Spring Boot 1.4 supports this annotation.

Related

the #Mock and #InjectMocks annotations don't work

rest
#RestController
public class StackOverFlowController {
private StackOverFlowService service;
#Autowired
public StackOverFlowController(
#Qualifier(value = "stackOverFlowServiceImpl")
StackOverFlowService service) {
this.service = service;
}
#RequestMapping("api/stackoverflow")
public List<StackOverFlowDto> getListOfProviders() throws URISyntaxException {
List<StackOverFlowDto> allSites = service.getAllSites();
return allSites;
}
}
service
public interface StackOverFlowService {
List<StackOverFlowDto> getAllSites() throws URISyntaxException;
List<StackOverFlowDto> getAllSitesByTitle(String title) throws URISyntaxException;
}
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${version.mapstruct}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
test
#RunWith(MockitoJUnitRunner.class)
public class StackOverFlowControllerTest {
#Mock
private StackOverFlowService service ;
#InjectMocks
private StackOverFlowController controller;
#Test
public void getListOfProviders() throws URISyntaxException {
List<StackOverFlowDto> allSites = service.getAllSites();
when(service.getAllSites()).thenReturn(ImmutableList.of());
List<StackOverFlowDto> listOfProviders = controller.getListOfProviders();
verify(service).getAllSites();
}
}
I can create an mock-object this way: :
private StackOverFlowService service = Mockito.mock(StackOverFlowService.class);
update 1
update 2
Wnen I have done this:
#RunWith(SpringRunner.class)
#WebMvcTest(StackOverFlowController.class)
public class StackOverFlowControllerTest {
#Mock
private StackOverFlowService service;
#InjectMocks
StackOverFlowController controller;
#Test
public void getListOfProviders() throws URISyntaxException {
List<StackOverFlowDto> allSites = service.getAllSites();
when(service.getAllSites()).thenReturn(ImmutableList.of());
List<StackOverFlowDto> listOfProviders = controller.getListOfProviders();
verify(service).getAllSites();
}
}
I got an error:
PM org.junit.vintage.engine.descriptor.RunnerTestDescriptor warnAboutUnfilterableRunner WARNING: Runner
org.junit.internal.runners.ErrorReportingRunner (used on class
com.spring.mongo.web.contollers.stackoverflow.StackOverFlowControllerTest)
does not support filtering and will therefore be run completely.
org.junit.runners.model.InvalidTestClassError: Invalid test class
'com.spring.mongo.web.contollers.stackoverflow.StackOverFlowControllerTest':
No runnable methods
where is the method get() from ?
update 3
This the test was performed:
#WebMvcTest(StackOverFlowController.class)
public class StackOverFlowControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private StackOverFlowService service;
#MockBean
private StackOverFlowController controller;
#Test
public void getListOfProviders() throws Exception {
when(service.getAllSites()).thenReturn(ImmutableList.of());
List<StackOverFlowDto> listOfProviders = controller.getListOfProviders();
assertNotNull(listOfProviders);
}
}
But I don't see the point. I don't get any data:
List<StackOverFlowDto> listOfProviders = controller.getListOfProviders();
But here I should get a collection of elements, but I get 0
Update 4
It is my the understanding:
when(service.getAllSites()).thenReturn(ImmutableList.of());
We test a controller. We don't need below layers. We need to validate that the method
getListOfProviders()
of a contoller is called.
So,
service.getAllSites()
here we point Mockito-object
#MockBean
private StackOverFlowService service;
This method should return some sort of collection.
thenReturn(ImmutableList.of())
it should return empty collection.
Why ?
I don't understand this.
List<StackOverFlowDto> listOfProviders = controller.getListOfProviders();
What is it for?
Update 5
#Test
public void getListOfProviders() throws Exception {
when(service.getAllSites()).thenReturn(ImmutableList.of());
List<StackOverFlowDto> listOfProviders = controller.getListOfProviders();
assertNotNull(listOfProviders);
verify(service).getAllSites();
}
verify(service).getAllSites();
This should validate that the method getAllSites() from interface StackOverFlowService (from its implemetation), will call...
But Again I got errors:
Wanted but not invoked:
com.spring.mongo.service.read.stackoverflow.StackOverFlowService#0
bean.getAllSites();
-> at com.spring.mongo.web.contollers.stackoverflow.StackOverFlowControllerTest.getListOfProviders(StackOverFlowControllerTest.java:38)
Actually, there were zero interactions with this mock.
Wanted but not invoked:
com.spring.mongo.service.read.stackoverflow.StackOverFlowService#0
bean.getAllSites();
-> at com.spring.mongo.web.contollers.stackoverflow.StackOverFlowControllerTest.getListOfProviders(StackOverFlowControllerTest.java:38)
Actually, there were zero interactions with this mock.
Why ?
Update 6
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {SpringMongoApplication.class})
#WebMvcTest(StackOverFlowController.class)
class StackOverFlowControllerTestUnit {
#Autowired
private MockMvc mockMvc;
#MockBean
private StackOverFlowService service;
#MockBean
private StackOverFlowController controller;
#Test
void getListOfProviders() throws Exception {
MockHttpServletRequestBuilder requestBuilder =
MockMvcRequestBuilders
.get("/api/stackoverflow")
.accept(MediaType.APPLICATION_JSON);
mockMvc.perform(requestBuilder)
.andDo(print())
.andExpect(status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.id").exists())
.andDo(print());
}
}
Here beans is created, but i don't result into json...
.andExpect(MockMvcResultMatchers.jsonPath("$.sites").exists())
.andExpect(MockMvcResultMatchers.jsonPath("$.sites[*].id").isNotEmpty());
Update_7
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {SpringMongoApplication.class})
#WebMvcTest(StackOverFlowController.class)
public class StackOverFlowControllerTestFromForum {
#Autowired
private MockMvc mockMvc;
#MockBean
private StackOverFlowService service;
#InjectMocks
private StackOverFlowController controller;
#Test
public void getListOfProviders() throws Exception {
MockHttpServletRequestBuilder requestBuilder =
MockMvcRequestBuilders
.get("/api/stackoverflow")
.accept(MediaType.APPLICATION_JSON);
mockMvc.perform(requestBuilder)
.andDo(print())
.andExpect(status().isOk());
}
}
In this version of the code, all beans are initialized without errors.
#InjectMocks - It is work also.
Normal integration testing offered by Spring simply creates objects
as if the application were running in production. I still don't
understand what Mock-objects are for.
Here any ideas is finished.
I don't understand why it dosen't work.
Сan someone explain the idea of #Mock and #InjectMocks annotations working in relation to this example ?
Does anyone have any ideas?
If you want the controller test to be the same like any other unit test case class then use spring for running tests using annotation #RunWith(SpringRunner.class) .Since you are writing the unit test case for the controller , use the test method like below.You just need to mock the service call and call the controller method or more appropriately hit it via mock mvc :
#Test
public void getListOfProviders() throws URISyntaxException {
when(service.getAllSites()).thenReturn(ImmutableList.of());
List<StackOverFlowDto> listOfProviders = controller.getListOfProviders();
assertNotNull(listOfProviders);
}
But if you want it to be the right way then :
#WebMvcTest
public class StackOverFlowControllerTest {
#Mock
private StackOverFlowService service ;
#Autowired
private MockMvc mockMvc;
#Test
public void getListOfProviders() throws URISyntaxException {
when(service.getAllSites()).thenReturn(ImmutableList.of());
this.mockMvc.perform(get("api/stackoverflow")).andDo(print()).andExpect(status().isOk());
}
}
If you are having issues ith #WebMvcTest it means springboot is having trouble setting up the test env because of project structure or annotation on main application class that might be causing issues for test setup so in that case try setting up an entire application context like in an integration test (a bit tedious and unnecessary ) by using the #SpringBootTest annotation instead.
Official doc link.
Try with #RunWith(SpringRunner.class) instead of #RunWith(MockitoJUnitRunner.class), it should work. SpringRunner will help with Dependency Injection of the mocks.
Edit:
Try with the code below:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {SpringMongoApplication.class})
#WebMvcTest(StackOverFlowController.class)
public class StackOverFlowControllerTestUnit {
#Autowired
private MockMvc mockMvc;
#MockBean
private StackOverFlowService service;
#Test
void getListOfProviders() throws Exception {
MockHttpServletRequestBuilder requestBuilder =
MockMvcRequestBuilders
.get("/api/stackoverflow")
.accept(MediaType.APPLICATION_JSON);
mockMvc.perform(requestBuilder)
.andDo(print())
.andExpect(status().isOk());
// .andExpect(MockMvcResultMatchers.jsonPath("$.id").exists())
// .andDo(print());
}
}
Try going step by step. If the above works then maybe you can go for the json path verification

Spring Aspect doesn't work if bean is instantiated manually first

I have a simple aspect like below
#Aspect
public class PersistentAspect {
#AfterReturning("#annotation(org.aspect.PersistentOperation)")
public void log(JoinPoint jp) {
System.out.println("aspect call");
}
}
and an AppConfig like below
public class AppConfig {
private Integer num;
private String text;
public Integer getNum() {
return num;
}
#PersistentOperation
public void setNum(Integer num) {
this.num = num;
}
public String getText() {
return text;
}
#PersistentOperation
public void setText(String text) {
this.text = text;
}
}
And configuration class like below
#EnableWs
#Configuration
public class WsConfig extends WsConfigurerAdapter {
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
AppConfig config = config();
interceptors.add(new CustomValidatingInterceptor(schema(), null));
}
#Bean
public AppConfig config() {
AppConfig config = null;
config = new AppConfig();
return config;
}
#Bean
public PersistentAspect persistentAspect() {
PersistentAspect persistentAspect = new PersistentAspect();
return persistentAspect;
}
}
If I use below in the addInterceptors
AppConfig config = config();
The Aspect will not work. The obvious solution I have is to change the code to
AppConfig config = new AppConfig();
Now what I want to understand is, is there a config in which AppConfig config = config(); could still be made working. I assume that when spring initiates the Bean, it can make an AOP proxy of the AppConfig, and when I initiate the bean it interferes with that process somehow. What is the spring/spring-boot way of handling this?
Below is the pom.xml, so basically latest Spring 5.0.5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
Edit-1
Before posting the I had already adding a #EnableAspectJAutoProxy, but that had not helped.
The git repo to try the issue is on below
https://github.com/tarunlalwani/spring-aop-so-50084308
Your AppConfig bean (which you initialize, correctly, using a method annotated with #Bean) needs to be wrapped in a proxy that then executes the aspect logic.
You have to enable this behavior by adding the #EnableAspectJAutoProxy annotation to your WsConfig class.
By the way: it is totally correct to use
AppConfig config = config();
in your addInterceptors method. Spring will return the bean that was created by the config method.

How to set default properties in spring dataset DatabaseConfig

I am using springtestdbunit for writing test cases for my repository/dao.
I am populating data using db scripts and then matching with expected-sql-scripts.
I want to set escape character for my sample sql scripts.
My sample test class looks like this
#RunWith(SpringJUnit4ClassRunner.class)
#TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class,
DbUnitTestExecutionListener.class})
#DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
#DatabaseSetup({"notification-init.xml"})
#SpringBootTest(classes = {TestApplication.class, DaoConfig.class})
public class NotificationRepositoryTest {
#Autowired
private NotificationRepository notificationRepository;
#After
#DatabaseTearDown
public void tearDown() throws Exception {
}
#Before
public void setUp() throws Exception {
//donorModel = donorRepository.findOne(0L);
}
All I want to set these two properties when I am configuring my H2 database.
DatabaseConfig.PROPERTY_ESCAPE_PATTERN
DatabaseConfig.FEATURE_ALLOW_EMPTY_FIELDS
Here is an example from a spring boot project using JavaConfig to set those 2 properties:
#Configuration
#Profile("test")
public class DBUnitConfiguration {
#Bean
public DatabaseConfigBean dbUnitDatabaseConfig() {
DatabaseConfigBean bean = new DatabaseConfigBean();
bean.setAllowEmptyFields(true);
bean.setEscapePattern("\"?\"");
return bean;
}
#Bean
public DatabaseDataSourceConnectionFactoryBean dbUnitDatabaseConnection(
DatabaseConfigBean dbUnitDatabaseConfig,
DataSource dataSource
) {
DatabaseDataSourceConnectionFactoryBean bean = new DatabaseDataSourceConnectionFactoryBean(dataSource);
bean.setDatabaseConfig(dbUnitDatabaseConfig);
return bean;
}
}
It requires 1.3.0 version of spring-test-dbunit to set the ALLOW_EMPTY_FIELDS prop:
<dependency>
<groupId>com.github.springtestdbunit</groupId>
<artifactId>spring-test-dbunit</artifactId>
<version>1.3.0</version>
<scope>test</scope>
</dependency>

#Autowired does not work with #Configurable

I am trying to do an image upload API. I have a ImageUpload task as follows,
#Component
#Configurable(preConstruction = true)
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class ImageUploadTask implements Callable<JSONObject> {
#Autowired
private ImageUploadService imageUploadService;
#Override
public JSONObject call() throws Exception {
....
//Upload image via `imageUploadService`
imageUploadService.getService().path('...').post('...'); // Getting null pointer here for imageUploadService which is a WebTarget
}
}
The ImageUploadService looks like the below,
#Component
public class ImageUploadService {
#Inject
#EndPoint(name="imageservice") //Custom annotation, battle tested and works well for all other services
private WebTarget imageservice;
public WebTarget getService() {
return imageservice;
}
}
Here is the spring boot application class,
#ComponentScan
#EnableSpringConfigured
#EnableLoadTimeWeaving(aspectjWeaving=EnableLoadTimeWeaving.AspectJWeaving.ENABLED)
#EnableAutoConfiguration
public class ImageApplication extends SpringBootServletInitializer {
#Bean
public InstrumentationLoadTimeWeaver loadTimeWeaver() throws Throwable {
InstrumentationLoadTimeWeaver loadTimeWeaver = new InstrumentationLoadTimeWeaver();
return loadTimeWeaver;
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
servletContext.addListener(new RequestContextListener());
}
public static void main(String[] args) throws IOException {
SpringApplication.run(ImageApplication.class);
}
}
Additional information :
Spring version of dependencies are at 4.2.5.RELEASE
pom.xml has dependencies added for spring-aspects and
spring-instrument
I am getting a NullPointerException in ImageUploadTask. My suspicion is that #Autowired doesn't work as expected.
Why wouldn't work and how do I fix this?
Is it mandatory to use #Autowired only when I use #Conigurable, why not use #Inject? (though I tried it and getting same NPE)
By default the autowiring for the #Configurable is off i.e. Autowire.NO beacuse of which the imageUploadService is null
Thus update the code to explicity enable it either as BY_NAME or BY_TYPE as below.
#Component
#Configurable(preConstruction = true, autowire = Autowire.BY_NAME)
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class ImageUploadTask implements Callable<JSONObject> { .... }
Rest of the configuration viz. enabling load time weaving seems fine.
Also regarding #Inject annotation have a look here which pretty much explains the difference (or similarity perhaps)

Dependency Injection in JSR-303 Constraint Validator with Spring fails

I have the same problem as here and here but couldn't find a solution yet.
So my sample test project will show the whole relevant configuration and code:
Constraint annotation:
#Target({ ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = FooValidator.class)
public #interface FooValid {
String message();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Annotated PoJo:
public class Foo {
#FooValid(message = "Test failed")
private Integer test;
[...]
}
Annotated Service with #Validated:
#Service
#Validated
public class FooService {
private final Test test;
#Autowired
public FooService(final Test test) {
this.test = test;
}
public void foo(#Valid final Foo foo) {
this.test.test(foo);
}
}
JSR-303 ConstraintValidator:
public class FooValidator implements ConstraintValidator<FooValid, Integer> {
#Autowired
private ValidationService validationService;
#Override
public void initialize(final FooValid constraintAnnotation) {
// TODO Auto-generated method stub
}
#Override
public boolean isValid(final Integer value, final ConstraintValidatorContext context) {
// this.validationService is always NULL!
Assert.notNull(this.validationService, "the validationService must not be null");
return false;
}
}
Injected ValidationService:
#Service
public class ValidationService {
public void test(final Foo foo) {
System.out.println(foo);
}
}
Spring boot application and configuration:
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class Application {
public static void main(final String[] args) {
final ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
final FooService service = context.getBean(FooService.class);
service.foo(new Foo());
}
#Bean
public static LocalValidatorFactoryBean validatorFactory() {
return new LocalValidatorFactoryBean();
}
#Bean
public static MethodValidationPostProcessor validationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
relevant maven pom:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.1.9.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>demo.Application</start-class>
<java.version>1.7</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
I'm using the LocalValidatorFactoryBean with the default SpringConstraintValidatorFactory.
But why the dependency injection is not working in the ConstraintValidator and the ValidationService could not be autowired?
By the way if I don't use #Validated at the service, inject in opposite the spring or javax Validator interface and call manually "validator.validate" the dependency injection will work. But I don't want to call the validate method in every service manually.
Many thanks for help :)
I have fought the same problem in Spring Boot environment and I found out that Hibernate internal implementation got in instead of the configured Spring's one. When the application started, debugger caught a line with the Spring's factory but later in runtime there was Hibernate's one. After some debugging, I came to the conclusion that MethodValidationPostProcessor got the internal one. Therefore I configured it as follows:
#Bean
public Validator validator() {
return new LocalValidatorFactoryBean();
}
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor(Validator validator) {
MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(validator);
return methodValidationPostProcessor;
}
Note the setter for validator - it did the job.
I had the same issue. The problem is arises because Hibernate is finding and applying the validators instead of Spring. So you need to set validation mode to NONE when configuring Hibernate:
#Bean(name="entityManagerFactory")
public LocalContainerEntityManagerFactoryBean
localContainerEntityManagerFactoryBean(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean lcemfb =
new LocalContainerEntityManagerFactoryBean();
lcemfb.setDataSource(dataSource);
lcemfb.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
lcemfb.setValidationMode(ValidationMode.NONE);
// Continue configuration...
If you have confgured a LocalValidationFactoryBean Spring will pick up any validators annotated with #Component and autowire them.
This is what worked for me. I had to used the #Inject tag.
public class FooValidator implements ConstraintValidator<FooValid, Integer> {
private ValidationService validationService;
#Inject
public FooValidator(ValidationService validationService){
this.validationService = validationService;
}
#Override
public void initialize(final FooValid constraintAnnotation) {
// TODO Auto-generated method stub
}
#Override
public boolean isValid(final Integer value, final ConstraintValidatorContext context) {
// this.validationService is always NULL!
Assert.notNull(this.validationService, "the validationService must not be null");
return false;
}
}

Resources