MockMvc 预期 200 实际 400 - @Valid 注释未得到尊重

问题描述 投票:0回答:1

我们最近将应用程序升级到 Spring 6、java JDK21,现在正在使用

jakarta.validation-api
。此外,我们还有一个扩展
ResponseEntityExceptionHandler
的自定义类。当我们启动应用程序以测试错误请求时,我们确实会调用
handleMethodArgumentNotValid()
方法,因此测试类之外不会出现问题。我们正在测试类中使用独立的mockMvc 实现来测试此方法重写。当我们对控制器执行请求时,应该调用
@Valid
,因为意识到
@RequestBody
缺少参数,但控制器仍然返回 200。

spring 5 和 spring 6 之间发生了什么变化,我们无法利用

MockMvc
独立在测试类中调用
@Valid
?有人遇到过这种情况吗

我们尝试在调用

MethodArgumentNotValidException
中的服务类时抛出
FakeController
,但这会引发 500 错误。这是有道理的。 spring 5 和 6 之间肯定发生了一些变化。我们确实看到 HandlerMethod 中有一个名为
validateArguments
的布尔值,但我们无法在测试类中将其设置为 true。

下面是测试类和相关依赖版本

package com.mock.controller;

import static org.junit.Assert.assertEquals;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;

import org.apache.commons.lang.StringEscapeUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.ExceptionHandlerMethodResolver;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod;

import com.fasterxml.jackson.databind.ObjectMapper;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;

public class ExceptionHandlerTest {

    @Mock private MockService service;
    
    private ObjectMapper mapper;
    private MockMvc mvc;
    private MockController controller;
    
    private ExceptionHandlerExceptionResolver createExceptionResolver(boolean is500Disabled) {
        ExceptionHandlerExceptionResolver exceptionResolver = new ExceptionHandlerExceptionResolver() {
            @Override
            protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
                Method method = new ExceptionHandlerMethodResolver(CustomGlobalExceptionHandler.class).resolveMethod(exception);
                CustomGlobalExceptionHandler handler = new CustomGlobalExceptionHandler();
                handler.setDisable500(is500Disabled);
                return new ServletInvocableHandlerMethod(handler, method);
            }
        };
        exceptionResolver.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
        exceptionResolver.afterPropertiesSet();
        return exceptionResolver;
    }
    

    @Before
    public void setup() {
        MockitoAnnotations.openMocks(this);
        controller = new MockController(service);
        mvc = MockMvcBuilders.standaloneSetup(controller)
                .setHandlerExceptionResolvers(createExceptionResolver(false))
                .build();
        mapper = new ObjectMapper();
    }
    
    @Test
    public void thatMethodArgumentNotValidExceptionHappens() throws Exception {        
        Request req = new Request(null, "brand", "model", "color", "qty");

        MvcResult result = mvc.perform(post("/api/some/post").contentType(MediaType.APPLICATION_JSON_VALUE).content(mapper.writeValueAsString(req)))
                .andExpect(status().isBadRequest())
                .andReturn();
        
        assertEquals(HttpStatus.BAD_REQUEST.value(), result.getResponse().getStatus());
        JSONObject obj = new JSONObject(UnescapeResponseJSON(result));
        assertEquals("Error Processing Input", obj.get("detail"));
        assertEquals("id cannot be empty or blank", ((JSONArray)obj.get("errors")).get(0));
        assertEquals("Error Processing Input "+((JSONArray)obj.get("errors")).get(0), obj.get("message"));
    }
    
    private String UnescapeResponseJSON(MvcResult result) throws UnsupportedEncodingException {
        String unescapedJSON  = StringEscapeUtils.unescapeJava(result.getResponse().getContentAsString());
        return unescapedJSON.replaceAll("^\"|\"$", "");
    }
@RestController
public class MockController {
    private MockService service;
    public MockController(MockService service) {this.service = service;}

    @PostMapping(value = "/api/some/post", consumes=MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Object> post(@Valid @RequestBody Request req,  BindingResult bindingResult) {
        return new ResponseEntity<>(service.get(10), HttpStatus.OK);
    }        
}
public class MockService {
    public Object get(int i) {
        return new Object();
    }
}
public class Request {
    
    @NotNull(message = "id cannot be empty or blank")
    private String id;
    @NotNull(message = "brand cannot be empty or blank")
    private String brand;
    @NotNull(message = "model cannot be empty or blank")
    private String model;
    @NotNull(message = "color cannot be empty or blank")
    private String color;
    @NotNull(message = "qty cannot be empty or blank")
    private String qty;
    
    public Request() {}
    public Request(String id, String brand, String model, String color, String qty) {
        this.id = id;
        this.brand = brand;
        this.model = model;
        this.color = color;
        this.qty = qty;
    }
    
    //omitted getters and setters
}
<dependency>
  <groupId>jakarta.validation</groupId>
  <artifactId>jakarta.validation-api</artifactId>
  <version>3.1.0-M2</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-test</artifactId>
  <version>6.1.5</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>6.1.5</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>6.1.5</version>
</dependency>
spring mockmvc jakarta-validation
1个回答
0
投票

我在执行 Spring 5 -> 6 升级时遇到了同样的问题。

修复方法是使用

LocalValidatorFactoryBean
设置验证器,并确保表达式工厂以及 hibernate 本身具有相关的运行时依赖项。

MockMvcBuilders.standaloneSetup(new MyController())
    .setValidator(new LocalValidatorFactoryBean())
    .build()
testRuntimeOnly("org.glassfish:jakarta.el:4.0.2")
© www.soinside.com 2019 - 2024. All rights reserved.