SpringBoot自定义约束校验器单元测试报“No target Validator set”错误
我有一个SpringBoot应用,通过如下方式注入LocalValidatorFactoryBean:
private LocalValidatorFactoryBean validatorFactoryBean; @Autowired public void setValidatorFactoryBean(LocalValidatorFactoryBean validatorFactoryBean) { this.validatorFactoryBean = validatorFactoryBean; }
我在Car类上定义了自定义校验器,并通过以下代码执行校验:
Set<ConstraintViolation<Car>> constraintViolations = validatorFactoryBean.validate(myCar);
上述代码在应用中运行正常,但在TestNG单元测试中执行validatorFactoryBean.validate(myCar)时,出现“No target Validator set”错误。
我在单元测试中通过如下方式设置validatorFactoryBean:
@InjectMocks LocalValidatorFactoryBean validatorFactoryBean;
并通过调用setValidatorFactoryBean方法将其传入待测试类。调试发现,该validatorFactoryBean无法识别主代码中的所有ConstraintValidator实现。
我的汽车约束校验器实现如下:
public class CarConstraintImpl implements ConstraintValidator<CarConstraint, Request> { @Inject FuelDependency fuelDependency; public CarConstraintImpl() { } @Override public boolean isValid(Request req, ConstraintValidatorContext constraintContext) { try { fuelDependency.check(req); } catch (Exception e) { //some code } return true; } }
请问如何让validatorFactoryBean识别所有ConstraintValidator实现?
问题核心是@InjectMocks直接实例化的LocalValidatorFactoryBean没有经过Spring初始化,既无法扫描注册自定义ConstraintValidator,也没有创建内部的Validator实例,才会触发“No target Validator set”错误。以下是几种可行的解决方式:
1. 借助Spring Test上下文初始化校验器
在TestNG测试类上添加Spring相关注解,让Spring容器负责初始化LocalValidatorFactoryBean,它会自动扫描并注册所有自定义校验器,同时处理依赖注入:
@SpringBootTest public class YourTestClass { @Autowired private LocalValidatorFactoryBean validatorFactoryBean; @Autowired private YourServiceClass yourService; // 待测试类 @Test public void testValidation() { Car myCar = new Car(); Set<ConstraintViolation<Car>> violations = validatorFactoryBean.validate(myCar); // 编写断言或其他测试逻辑 } }
这种方式会启动完整的Spring上下文,适合集成测试,能保证校验器及其依赖(如FuelDependency)都被正确初始化。
2. 手动初始化LocalValidatorFactoryBean
如果想避免启动完整Spring上下文,可手动配置并初始化LocalValidatorFactoryBean,指定扫描包并处理依赖注入:
public class YourTestClass { private LocalValidatorFactoryBean validatorFactoryBean; private YourServiceClass yourService; @BeforeMethod public void setUp() { validatorFactoryBean = new LocalValidatorFactoryBean(); // 创建Spring Bean工厂,用于注入校验器中的依赖 DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); // 模拟FuelDependency实例(或根据需要注入真实实例) FuelDependency mockFuelDependency = Mockito.mock(FuelDependency.class); beanFactory.registerSingleton("fuelDependency", mockFuelDependency); validatorFactoryBean.setBeanFactory(beanFactory); // 指定自定义校验器所在的包路径,让校验器工厂扫描注册 validatorFactoryBean.setPackagesToScan("com.yourpackage.constraints"); validatorFactoryBean.setValidationMessageSource(new ResourceBundleMessageSource()); // 执行初始化,触发校验器扫描和内部Validator创建 validatorFactoryBean.afterPropertiesSet(); // 将初始化好的校验器注入待测试类 yourService = new YourServiceClass(); yourService.setValidatorFactoryBean(validatorFactoryBean); } @Test public void testValidation() { Car myCar = new Car(); Set<ConstraintViolation<Car>> violations = validatorFactoryBean.validate(myCar); // 编写测试逻辑 } }
这种方式适合单元测试,通过手动配置减少上下文启动开销,同时保证校验器正常工作。
3. Spring Test环境下用@MockBean替代@InjectMocks
如果已经在使用Spring Test但不想启动完整@SpringBootTest,可以用@MockBean模拟依赖,同时让Spring管理LocalValidatorFactoryBean:
@ContextConfiguration(classes = {YourValidationConfig.class}) public class YourTestClass extends AbstractTestNGSpringContextTests { @Autowired private LocalValidatorFactoryBean validatorFactoryBean; @MockBean private FuelDependency fuelDependency; @Autowired private YourServiceClass yourService; @Test public void testValidation() { // 编写测试逻辑 } }
配套的配置类YourValidationConfig用于注册相关Bean:
@Configuration public class YourValidationConfig { @Bean public LocalValidatorFactoryBean validatorFactoryBean() { LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean(); bean.setPackagesToScan("com.yourpackage.constraints"); return bean; } @Bean public YourServiceClass yourServiceClass() { return new YourServiceClass(); } }
内容的提问来源于stack exchange,提问作者developer747

