如何在 JUnit 测试中使用 Spring defaultValidator

How to use Spring defaultValidator in JUnit tests

我一直在我的测试 class 中使用 @SpringJunitConfig 来减少 @SpringBootTest 上的上下文加载时间。这在我只使用我自己的 classes 时效果很好,因为我可以轻松指定要加载的包/classes。

现在我正在尝试使用 Spring 的默认参数验证。根据其他 SO 答案,我创建并加载了 defaultValidator bean。但是,当我的测试调用带有验证的方法并且测试失败时,Spring 的默认验证不会被触发。我知道被测 class 上的注释是正确的,因为当我切换到 @SpringBootTest 时,测试通过了。

还有其他想法吗?

这是我最接近的一次,但 Spring 没有自动验证,除非我切换到 @SpringBootTest,它加载完整的上下文并且速度太慢。

测试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
        });
    }

服务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
}

defaultValidator bean 由 spring-boot 自动配置内容定义,如果有任何 @Configuration bean 被注释为 @EnableAutoConfiguration,它将被启用。

但是 @EnableAutoConfiguration 默认情况下会考虑所有由 spring-boot 定义的自动配置内容(即那些在 spring.factories 中定义的内容),这对于编写一个单元来说可能太多了测试。所以还有另一个名为 @ImportAutoConfiguration 的注释,它可以只导入并应用指定的自动配置 class.

由于 defaultValidator bean 是在 ValidationAutoConfiguration 中定义的,这意味着您可以简单地通过以下方式导入它:

@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 : 要找出哪个自动配置 class 定义了 defaultValidator bean ,你可以在 application.properties 中启用调试模式 :

debug=true

然后像往常一样启动spring-boot应用程序,它将打印出以下条件评估报告:

============================
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)