如何用JUnit正确测试Spring的@NotBlank校验?异常未抛出求助
问题根源
MethodArgumentNotValidException是Spring MVC控制器层触发的异常:当控制器方法参数标注了@Valid,Spring会自动校验DTO的约束注解(如@NotBlank),校验失败时抛出该异常。而你直接调用ProductService的方法,Service层本身没有校验逻辑,自然不会触发这个异常。
解决方案
根据测试目标不同,有两种可行方案:
方案1:测试完整控制器请求流程(推荐)
使用Spring的MockMvc模拟HTTP请求,完整触发MVC层的校验逻辑,验证异常处理后的响应结果。
示例测试代码:
@WebMvcTest(ProductController.class) class ProductControllerTest { @Autowired private MockMvc mockMvc; @MockBean private ProductService productService; @Test void whenEmptyNameProduct_shouldReturnBadRequest() throws Exception { // 构造无效请求 ProductRequest invalidRequest = new ProductRequest(); invalidRequest.setAmount(0); invalidRequest.setPrice(500.0); // 模拟POST请求并断言响应 mockMvc.perform(post("/product") .contentType(MediaType.APPLICATION_JSON) .content(new ObjectMapper().writeValueAsString(invalidRequest))) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.message").value("Name field is required")); } }
@WebMvcTest仅加载控制器相关Bean,避免启动完整Spring上下文,测试更高效。- 通过
MockMvc模拟请求,自动触发@Valid校验,最终验证全局异常处理器返回的错误响应。
方案2:单独测试DTO校验逻辑
如果只想验证ProductRequest的约束注解是否生效,可以直接使用Spring的校验器手动触发校验。
示例测试代码:
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; class ProductRequestValidationTest { private final LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); @BeforeEach void setUp() { validator.afterPropertiesSet(); // 初始化校验器 } @Test void whenEmptyName_shouldHaveValidationError() { ProductRequest request = new ProductRequest(); request.setAmount(0); request.setPrice(500.0); // 手动校验DTO Set<ConstraintViolation<ProductRequest>> violations = validator.validate(request); // 断言校验结果 assertFalse(violations.isEmpty()); ConstraintViolation<ProductRequest> nameViolation = violations.stream() .filter(v -> v.getPropertyPath().toString().equals("name")) .findFirst() .orElseThrow(); assertEquals("Name field is required", nameViolation.getMessage()); } }
可选:在Service层添加校验逻辑
如果希望Service层也能拦截无效参数,可以注入Validator手动触发校验,此时测试Service时会抛出ConstraintViolationException。
修改Service代码:
@Slf4j @Service @RequiredArgsConstructor public class ProductServiceImpl implements ProductService { private final ProductMapper productMapper; private final ProductRepository productRepository; private final Validator validator; // 注入Spring校验器 @Override @Transactional public ProductResponse createProduct(ProductRequest productRequest) { // 手动触发校验 Set<ConstraintViolation<ProductRequest>> violations = validator.validate(productRequest); if (!violations.isEmpty()) { throw new ConstraintViolationException(violations); } Product product = productMapper.mapToEntity(productRequest); Product savedProduct = productRepository.save(product); log.info("New product saved. Id = {}", savedProduct.getId()); return productMapper.mapToResponse(savedProduct); } }
修改Service测试代码:
@Test void whenEmptyNameProduct() { var productRequest = buildProduct(); var product = productMapper.mapToEntity(productRequest); when(productRepository.save(any())).thenReturn(product); // 断言ConstraintViolationException ConstraintViolationException thrown = assertThrows( ConstraintViolationException.class, () -> productService.createProduct(productRequest)); assertTrue(thrown.getConstraintViolations().stream() .anyMatch(v -> v.getMessage().equals("Name field is required"))); }
内容的提问来源于stack exchange,提问作者Luisian
相关产品推荐
相关产品推荐

