Issue
I have been using @SpringJunitConfig
on my test classes to reduce the context load time over @SpringBootTest
. This has worked well when I was using only my own classes as I can easily specify the packages / classes to be loaded.
Now I'm trying to use Spring's default argument validation. Based on other SO answers I have created and loaded defaultValidator
bean. However Spring's default validation is not triggered when my tests call a method with validations and the test fails. I know the annotations on the class under test are correct because when I switch to @SpringBootTest
the test passes.
Any additional ideas?
This is the closest I've come but no automatic validation by Spring occurs, unless I switch to @SpringBootTest
which loads the full context and is too slow.
Test Class
@SpringJUnitConfig()
class UserServiceImplTest {
@Configuration
@ComponentScan(basePackages = { "com.user" },
includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = { UserMapper.class,
UserServiceImpl.class}), useDefaultFilters = false)
static class ConfigMe {
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public Validator defaultValidator() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
return factory.getValidator();
}
}
@Autowired
private UserServiceImpl userService;
@Autowired
private UserMapper mapper;
@Autowired
@Qualifier("defaultValidator")
Validator validator;
@MockBean
private UserDao userDao;
// tests for UserServiceImpl that require valdiation of method arguments
// e.g. public UserDto findUser(@NotNull @Size(min = 30, max = 30) String userUnique)
@Test
void givenInvalidUnique_whenFind_thenConstraintException() {
assertThrows(ConstraintViolationException.class, () -> {
userService.findUser(null); // null
});
}
Service Class
@Service
@Transactional
@Validated
public class UserServiceImpl implements UserService {
@Override
@Transactional(readOnly = true)
public UserDto findUser(@NotNull @Size(min = 30, max = 30) String userUnique) {
log.trace("findUser called with unique [{}]", userUnique);
Optional<User> foundUser = userDao.findByUserUnique(userUnique);
if (foundUser.isEmpty())
throw new MyEntityNotFoundException(String.format("Could not find user with unique of [%s]", userUnique));
return mapper.UserEntityToDto(foundUser.get());
}
// other service methods
}
Solution
The defaultValidator
bean is defined by the spring-boot auto-configuration stuff which will be enabled if there are any @Configuration
bean annotated with @EnableAutoConfiguration
.
But @EnableAutoConfiguration
by default will consider all the auto-configuration stuff defined by spring-boot (i.e. those defined in spring.factories
) which may be too much for writing a unit test. So there are another annotation called @ImportAutoConfiguration
which can just import and apply a specified auto-configuration class.
As the defaultValidator
bean is defined in the ValidationAutoConfiguration
, that means you can simply import it by :
@SpringJUnitConfig
class UserServiceImplTest {
@Configuration
@ComponentScan(basePackages = { "com.user" }, includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = { UserMapper.class, UserServiceImpl.class}), useDefaultFilters = false)
@ImportAutoConfiguration(ValidationAutoConfiguration.class)
static class ConfigMe {
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
}
Tips : To find out which auto-configuration class defines defaultValidator
bean , you can enable debug mode in application.properties
:
debug=true
then start the spring-boot application as usual and it will print out the following conditions evaluation report :
============================
CONDITIONS EVALUATION REPORT
============================
Positive matches:
-----------------
ValidationAutoConfiguration matched:
- @ConditionalOnClass found required class 'javax.validation.executable.ExecutableValidator' (OnClassCondition)
- @ConditionalOnResource found location classpath:META-INF/services/javax.validation.spi.ValidationProvider (OnResourceCondition)
ValidationAutoConfiguration#defaultValidator matched:
- @ConditionalOnMissingBean (types: javax.validation.Validator; SearchStrategy: all) did not find any beans (OnBeanCondition)
Answered By - Ken Chan