如何在WebService的JUnit集成测试中忽略自定义校验注解?
如何在Spring集成测试中禁用自定义校验注解@DateInFuture?
你现在的场景是:有个校验未来日期的自定义注解@DateInFuture,在集成测试时需要用过去的日期来测试,得临时禁用这个校验。下面是几个可行的方案,你可以根据自己的项目情况选择:
方案1:Mock掉校验器(最简单直接)
利用Spring的@MockBean注解,把DateInFutureValidator替换成一个mock实例,让它的isValid方法总是返回true,相当于直接跳过校验逻辑。
修改你的测试类:
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class SpringItest { @Autowired protected MockMvc mvc; // 用MockBean替换原有的校验器 @MockBean private DateInFutureValidator dateInFutureValidator; @Test public void springItest() throws Exception { // 让mock的校验器总是返回true,跳过校验 Mockito.when(dateInFutureValidator.isValid(any(Temporal.class), any(ConstraintValidatorContext.class))) .thenReturn(true); mvc.perform(MockMvcRequestBuilders .post(path) .contentType(MediaType.APPLICATION_JSON) .content("{\"date\": \"2020-01-01\"}") // 传入过去的日期 .andExpect(status().isOk()); } }
这个方案不需要改动业务代码,只在测试层面修改,适合快速验证需求。
方案2:用测试专用配置替换校验器
如果不想用mock,也可以在测试类里定义一个测试专用配置,提供一个“空实现”的校验器:
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // 加载测试专用配置 @ContextConfiguration(classes = SpringItest.TestConfig.class) public class SpringItest { @Autowired protected MockMvc mvc; // 测试专用配置类 @Configuration static class TestConfig { @Bean public DateInFutureValidator dateInFutureValidator() { return new DateInFutureValidator() { @Override public boolean isValid(Temporal value, ConstraintValidatorContext context) { // 直接返回true,不执行原有校验逻辑 return true; } }; } } @Test public void springItest() throws Exception { mvc.perform(MockMvcRequestBuilders .post(path) .contentType(MediaType.APPLICATION_JSON) .content("{\"date\": \"2020-01-01\"}") .andExpect(status().isOk()); } }
这个方案比mock更贴近真实实现,适合需要自定义测试逻辑的场景。
方案3:利用校验分组(最规范的长期方案)
如果项目需要长期维护,推荐用校验分组的方式,把生产环境的校验和测试环境区分开:
首先修改@DateInFuture注解,指定它属于生产环境的分组:
@Target({ ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = DateInFutureValidator.class) public @interface DateInFuture { Class<?>[] groups() default {ProductionGroup.class}; // 指定生产分组 Class<? extends Payload>[] payload() default {}; // 定义生产环境分组标记接口 interface ProductionGroup {} }
然后修改控制器方法,用@Validated指定只在生产分组下触发校验:
@RestController public class MyController { @PostMapping(path) // 指定只校验ProductionGroup分组的注解 public Rsp post(@Validated(DateInFuture.ProductionGroup.class) MyDTO dto) { // 业务逻辑 } }
最后在测试类里,不需要指定生产分组,这样@DateInFuture的校验就不会触发:
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class SpringItest { @Autowired protected MockMvc mvc; @Test public void springItest() throws Exception { mvc.perform(MockMvcRequestBuilders .post(path) .contentType(MediaType.APPLICATION_JSON) .content("{\"date\": \"2020-01-01\"}") .andExpect(status().isOk()); } }
这个方案代码结构清晰,符合Spring校验的规范,适合长期迭代的项目。
方案4:利用Spring Profiles隔离环境
给你的校验器加上@Profile注解,让它只在非测试环境加载:
// 只在非test profile下加载这个校验器 @Profile("!test") public class DateInFutureValidator implements ConstraintValidator<DateInFuture, Temporal> { @Override public void initialize(DateInFuture constraintAnnotation) { } @Override public boolean isValid(Temporal value, ConstraintValidatorContext context) { // 原有的校验逻辑 } }
然后在测试类里激活test profile:
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles("test") // 激活test profile public class SpringItest { @Autowired protected MockMvc mvc; @Test public void springItest() throws Exception { mvc.perform(MockMvcRequestBuilders .post(path) .contentType(MediaType.APPLICATION_JSON) .content("{\"date\": \"2020-01-01\"}") .andExpect(status().isOk()); } }
这个方案适合需要严格区分测试和生产环境组件的场景,缺点是需要修改业务代码的注解。
内容的提问来源于stack exchange,提问作者membersound
相关产品推荐
相关产品推荐

