如何对带构造参数依赖的自定义Validator进行单元测试?
解决自定义Validator构造注入依赖的Mock测试问题
问题根源是Hibernate Validator默认的Validation.buildDefaultValidatorFactory()创建的Validator,不会处理Spring的依赖注入逻辑,既没法实例化带构造参数的Validator,也不会自动注入@Autowired的Bean,导致要么实例化失败,要么出现NPE。
下面提供两种可行方案,都能保留构造参数依赖并注入Mock的MyRestClient:
方案一:集成Spring Test环境(推荐)
通过Spring的测试框架加载最小化的上下文,让Spring帮我们注入Mock依赖:
@ExtendWith(SpringExtension.class) @SpringBootTest(classes = {MyValidator.class}) // 仅加载必要类,提升测试速度 public class MyValidatorTest { @Autowired private Validator validator; @MockBean // 用Mock替换Spring容器中的MyRestClient实例 private MyRestClient restClient; @Test public void testValidTaskDto() { // 准备测试数据 TaskDto taskDto = TaskDto.builder().taskId(1).build(); Map<String, Variable> mockVars = Map.of("testKey", new Variable()); when(restClient.getTaskVariables(1)).thenReturn(mockVars); // 执行验证 Set<ConstraintViolation<TaskDto>> violations = validator.validate(taskDto); // 断言验证通过 assertThat(violations).isEmpty(); } @Test public void testInvalidTaskDto() { TaskDto taskDto = TaskDto.builder().taskId(1).build(); when(restClient.getTaskVariables(1)).thenReturn(Collections.emptyMap()); Set<ConstraintViolation<TaskDto>> violations = validator.validate(taskDto); // 断言验证不通过 assertThat(violations).isNotEmpty(); } }
这种方式的优势是完全贴合实际运行环境,Spring会自动处理依赖注入,不需要手动管理Validator的实例化。
方案二:手动构建Validator并注入Mock
如果不想启动Spring容器,可手动自定义ConstraintValidatorFactory,控制Validator的实例化逻辑:
@ExtendWith(MockitoExtension.class) public class MyValidatorTest { private Validator validator; @Mock private MyRestClient restClient; @BeforeEach public void setUp() { // 手动实例化自定义Validator,注入Mock的依赖 MyValidator customValidator = new MyValidator(restClient); // 自定义工厂,返回我们预先实例化好的Validator ConstraintValidatorFactory validatorFactory = new ConstraintValidatorFactory() { @Override public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) { if (key.equals(MyValidator.class)) { return (T) customValidator; } // 其他Validator用默认无参构造实例化 try { return key.getDeclaredConstructor().newInstance(); } catch (Exception e) { throw new RuntimeException("Failed to instantiate validator", e); } } @Override public void releaseInstance(ConstraintValidator<?, ?> instance) { // 测试环境无需额外释放逻辑 } }; // 构建带自定义工厂的Validator ValidatorFactory factory = Validation.byDefaultProvider() .configure() .constraintValidatorFactory(validatorFactory) .buildValidatorFactory(); validator = factory.getValidator(); } @Test public void testValidTaskDto() { TaskDto taskDto = TaskDto.builder().taskId(1).build(); when(restClient.getTaskVariables(1)).thenReturn(Map.of("testKey", new Variable())); Set<ConstraintViolation<TaskDto>> violations = validator.validate(taskDto); assertThat(violations).isEmpty(); } }
这种方式的优势是测试启动速度快,不需要加载Spring上下文,适合仅验证Validator核心逻辑的场景。
内容的提问来源于stack exchange,提问作者skiermax
相关产品推荐
相关产品推荐

