单元测试 MockHttpServletRequest 不返回内容类型
Unit test MockHttpServletRequest not returning content type
我想从我的 Java 类 申请 return JSON 对象(成功和失败的情况)。
我定义了一个 @RestControllerAdvice
来处理来自控制器的错误。我的程序在 json 中也正确显示了错误消息,但问题出在 单元测试 .
问题是什么时候抛出:
org.springframework.web.bind.MethodArgumentNotValidException
我的单元测试因错误而失败:
java.lang.AssertionError: Response header 'content-type' expected:<application/json;charset=UTF-8> but was:<null>
控制器:
@PostMapping("/import")
public ResponseEntity<StatusModel> import(@Valid @RequestBody ImportModel importModel ){
//logic
return new ResponseEntity<>(new StatusModel("Data accepted."), HttpStatus.OK);
}
单元测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {MockConfiguration.class})
@WebAppConfiguration
public class ModelControllerTest {
private MockMvc mockMvc;
@InjectMocks
private ModelController controller;
@Before
public void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
@Test
public void import_validRequest_imported() throws Exception {
mockMvc
.perform(
post("/import")
.content(VALID_CONTENT).contentType("application/json;charset=UTF-8"))
.andExpect(status().isOk())
.andExpect(header().string("content-type", "application/json;charset=UTF-8"))
.andExpect(jsonPath("$.status", equalTo("Data accepted")));
}
@Test
public void import_invalidRequest_notImported() throws Exception {
mockMvc
.perform(
post("/import")
.content(INVALID_CONTENT).contentType("application/json"))
.andExpect(status().isBadRequest())
.andDo(print())
.andExpect(header().string("content-type", "application/json")); <----- This assertion failed
}
}
MockHttpServletRequest 日志:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /import
Parameters = {}
Headers = {Content-Type=[application/json]}
Handler:
Type = com.test.ModelController
Method = public org.springframework.http.ResponseEntity<com.model.StatusModel> com.ModelController.import(com.test.model.ImportModel)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.web.bind.MethodArgumentNotValidException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 400
Error message = null
Headers = {}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
为什么是内容类型,错误信息是空的?
这是 mock mvc 不支持 spring 引导异常处理程序的原因,然后是推荐和修复。
理性摘录
Spring Boot's error handling is based on Servlet container error
mappings that result in an ERROR dispatch to an ErrorController.
MockMvc however is container-less testing so with no Servlet container
the exception simply bubbles up with nothing to stop it.
So MockMvc tests simply aren't enough to test error responses
generated through Spring Boot. I would argue that you shouldn't be
testing Spring Boot's error handling. If you're customizing it in any
way you can write Spring Boot integration tests (with an actual
container) to verify error responses. And then for MockMvc tests focus
on fully testing the web layer while expecting exceptions to bubble
up.
This is a typical unit vs integration tests trade off. You do unit
tests even if they don't test everything because they give you more
control and run faster.
推荐摘录
How can we write tests for error conditions using default spring-boot
JSON responses, then?
@xak2000 Rossen's already covered this, but I wanted to give you a
direct answer. If you really want to test the precise format of the
error response then you can use an integration test using
@SpringBootTest configured with a DEFINED_PORT or RANDOM_PORT web
environment and TestRestTemplate.
这里有完整的细节
https://github.com/spring-projects/spring-boot/issues/7321
修复
这里是使用 Spring 引导测试进行的错误验证略有不同。
import org.json.JSONException;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringBootTest(classes = DemoApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ModelControllerTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
void import_invalidRequest_notImported() throws JSONException {
String expected = "{\"status\":400,\"error\":\"Bad Request\",\"message\":\"JSON parse error: Unrecognized token 'Invalid': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false'); nested exception is com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'Invalid': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')\n at [Source: (PushbackInputStream); line: 1, column: 8]\",\"path\":\"/import\"}";
String invalidJson = "Invalid";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(invalidJson, headers);
ResponseEntity<String> response = restTemplate.exchange("/import", HttpMethod.POST, entity, String.class);
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
assertEquals(MediaType.APPLICATION_JSON, response.getHeaders().getContentType());
JSONAssert.assertEquals(expected, response.getBody(), false);
}
}
参考这里
https://mkyong.com/spring-boot/spring-rest-integration-test-example/
我想从我的 Java 类 申请 return JSON 对象(成功和失败的情况)。
我定义了一个 @RestControllerAdvice
来处理来自控制器的错误。我的程序在 json 中也正确显示了错误消息,但问题出在 单元测试 .
问题是什么时候抛出:
org.springframework.web.bind.MethodArgumentNotValidException
我的单元测试因错误而失败:
java.lang.AssertionError: Response header 'content-type' expected:<application/json;charset=UTF-8> but was:<null>
控制器:
@PostMapping("/import")
public ResponseEntity<StatusModel> import(@Valid @RequestBody ImportModel importModel ){
//logic
return new ResponseEntity<>(new StatusModel("Data accepted."), HttpStatus.OK);
}
单元测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {MockConfiguration.class})
@WebAppConfiguration
public class ModelControllerTest {
private MockMvc mockMvc;
@InjectMocks
private ModelController controller;
@Before
public void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
@Test
public void import_validRequest_imported() throws Exception {
mockMvc
.perform(
post("/import")
.content(VALID_CONTENT).contentType("application/json;charset=UTF-8"))
.andExpect(status().isOk())
.andExpect(header().string("content-type", "application/json;charset=UTF-8"))
.andExpect(jsonPath("$.status", equalTo("Data accepted")));
}
@Test
public void import_invalidRequest_notImported() throws Exception {
mockMvc
.perform(
post("/import")
.content(INVALID_CONTENT).contentType("application/json"))
.andExpect(status().isBadRequest())
.andDo(print())
.andExpect(header().string("content-type", "application/json")); <----- This assertion failed
}
}
MockHttpServletRequest 日志:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /import
Parameters = {}
Headers = {Content-Type=[application/json]}
Handler:
Type = com.test.ModelController
Method = public org.springframework.http.ResponseEntity<com.model.StatusModel> com.ModelController.import(com.test.model.ImportModel)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.web.bind.MethodArgumentNotValidException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 400
Error message = null
Headers = {}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
为什么是内容类型,错误信息是空的?
这是 mock mvc 不支持 spring 引导异常处理程序的原因,然后是推荐和修复。
理性摘录
Spring Boot's error handling is based on Servlet container error mappings that result in an ERROR dispatch to an ErrorController. MockMvc however is container-less testing so with no Servlet container the exception simply bubbles up with nothing to stop it.
So MockMvc tests simply aren't enough to test error responses generated through Spring Boot. I would argue that you shouldn't be testing Spring Boot's error handling. If you're customizing it in any way you can write Spring Boot integration tests (with an actual container) to verify error responses. And then for MockMvc tests focus on fully testing the web layer while expecting exceptions to bubble up.
This is a typical unit vs integration tests trade off. You do unit tests even if they don't test everything because they give you more control and run faster.
推荐摘录
How can we write tests for error conditions using default spring-boot JSON responses, then?
@xak2000 Rossen's already covered this, but I wanted to give you a direct answer. If you really want to test the precise format of the error response then you can use an integration test using @SpringBootTest configured with a DEFINED_PORT or RANDOM_PORT web environment and TestRestTemplate.
这里有完整的细节 https://github.com/spring-projects/spring-boot/issues/7321
修复
这里是使用 Spring 引导测试进行的错误验证略有不同。
import org.json.JSONException;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringBootTest(classes = DemoApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ModelControllerTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
void import_invalidRequest_notImported() throws JSONException {
String expected = "{\"status\":400,\"error\":\"Bad Request\",\"message\":\"JSON parse error: Unrecognized token 'Invalid': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false'); nested exception is com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'Invalid': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')\n at [Source: (PushbackInputStream); line: 1, column: 8]\",\"path\":\"/import\"}";
String invalidJson = "Invalid";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(invalidJson, headers);
ResponseEntity<String> response = restTemplate.exchange("/import", HttpMethod.POST, entity, String.class);
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
assertEquals(MediaType.APPLICATION_JSON, response.getHeaders().getContentType());
JSONAssert.assertEquals(expected, response.getBody(), false);
}
}
参考这里 https://mkyong.com/spring-boot/spring-rest-integration-test-example/