Spring 启动测试:使用 Jackson ObjectMapper 将模型转换为 JSON 字符串时 UserControllerTest 失败
Spring Boot Test: UserControllerTest Fails When Using Jackson ObjectMapper To Convert Model To JSON String
我正在 Spring Boot 中测试 @RestContoller
,它有一个 @PostMapping
方法,方法 @RequestBody
使用 @Valid
注释进行验证。为了测试它,我正在使用 MockMvc
并且为了填充请求正文内容我正在使用 Jackson ObjectMapper
;然而,当模型通过时,测试失败:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /api/user/register
Parameters = {}
Headers = [Content-Type:"application/json"]
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = com.springboottutorial.todoapp.controller.UserController
Method = public org.springframework.http.ResponseEntity<java.lang.String> com.springboottutorial.todoapp.controller.UserController.register(com.springboottutorial.todoapp.dao.model.User)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.http.converter.HttpMessageNotReadableException
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 = []
java.lang.AssertionError: Status
Expected :200
Actual :400
用户模型:
@Entity
@Table(name = "users",
uniqueConstraints = @UniqueConstraint(columnNames = {"EMAIL"}))
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private long id;
@Column(name = "FIRST_NAME")
@NotNull
private String firstName;
@Column(name = "LAST_NAME")
@NotNull
private String lastName;
@Column(name = "EMAIL")
@NotNull
@Email
private String emailAddress;
@Column(name = "PASSWORD")
@NotNull
private String password;
@Column(name = "CREATED_AT")
@NotNull
@Convert(converter = LocalDateTimeConveter.class)
private LocalDateTime createdAt;
@Column(name = "UPDATED_AT")
@NotNull
@Convert(converter = LocalDateTimeConveter.class)
private LocalDateTime updatedAt;
public User(@NotNull String firstName, @NotNull String lastName,
@NotNull @Email String emailAddress, @NotNull String password,
@NotNull LocalDateTime createdAt, @NotNull LocalDateTime updatedAt) {
this.firstName = firstName;
this.lastName = lastName;
this.emailAddress = emailAddress;
this.password = password;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
//setters and getters: omitted
用户控制器:
@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
UserService userService;
@PostMapping("/register")
public ResponseEntity<String> register(@RequestBody @Valid User user){
userService.createUser(user);
return ResponseEntity.ok().build();
}
}
用户控制器测试:
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc(addFilters = false)
public class UserControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void whenRequestValid_thenReturnStatusOk() throws Exception{
User user = new User("John", "QPublic", "john.public@gmail.com",
"123456789", LocalDateTime.now(), LocalDateTime.now());
mockMvc.perform(MockMvcRequestBuilders.post("/api/user/register")
.content(new ObjectMapper().writeValueAsString(user))
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
当我手动构建 JSON 字符串时,测试通过:
String json = "{\n" +
"\t\"firstName\" : \"John\",\n" +
"\t\"lastName\" : \"QPublic\",\n" +
"\t\"password\" : \"123456789\",\n" +
"\t\"createdAt\" : \"2016-11-09T11:44:44.797\",\n" +
"\t\"updatedAt\" : \"2016-11-09T11:44:44.797\",\n" +
"\t\"emailAddress\" : \"john.public@gmail.com\"\n" +
"}";
mockMvc.perform(MockMvcRequestBuilders.post("/api/user/register")
.content(json)
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(MockMvcResultMatchers.status().isOk());
这就是我实现其余控制器测试的方式。或许能帮到你。
我有这个摘要 class 来封装关于 JSON 映射的常见测试功能。
import lombok.SneakyThrows;
import lombok.val;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.http.MockHttpOutputMessage;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import static org.junit.Assert.assertNotNull;
public abstract class RestControllerTest {
private final MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(),
StandardCharsets.UTF_8);
private HttpMessageConverter messageConverter;
protected MediaType getContentType() {
return contentType;
}
@Autowired
void setConverters(HttpMessageConverter<?>[] converters) {
messageConverter = Arrays.stream(converters)
.filter(hmc -> hmc instanceof MappingJackson2HttpMessageConverter)
.findAny()
.orElse(null);
assertNotNull("the JSON message converter must not be null",
messageConverter);
}
@SneakyThrows
protected String toJson(Object data) {
val mockHttpOutputMessage = new MockHttpOutputMessage();
messageConverter.write(
data, MediaType.APPLICATION_JSON, mockHttpOutputMessage);
return mockHttpOutputMessage.getBodyAsString();
}
}
您可以像这样在测试中使用它class
@WebMvcTest(UserController.class)
@AutoConfigureMockMvc(addFilters = false)
public class UserControllerTest extends RestControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void whenRequestValid_thenReturnStatusOk() throws Exception{
User user = new User("John", "QPublic", "john.public@gmail.com",
"123456789", LocalDateTime.now(), LocalDateTime.now());
mockMvc.perform(MockMvcRequestBuilders.post("/api/user/register")
.content(toJson(user))
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
希望对你有用
正如 chrylis 在评论中提到的,问题的发生是由于 Java 8 日期和时间 API 和 Jackson 序列化冲突。默认情况下,ObjectMapper
不理解 LocalDateTime
数据类型,所以我需要向我的 maven 添加 com.fasterxml.jackson.datatype:jackson-datatype-jsr310
依赖项,这是一个数据类型模块,让 Jackson 识别 Java 8日期和时间 API 数据类型。 and a blog post 帮助我找出了我的问题所在。
如果LocalDateTime
不是问题,我认为我们应该在用户class中实现equals
。
Spring 不一定为您提供 "vanilla" ObjectMapper 实例。通过让 Spring 将 ObjectMapper 实例注入测试而不是使用默认构造函数创建 ObjectMapper,您将获得一个与您的实际 运行 时间环境相匹配的实例,前提是您的 spring您的单元测试配置文件设置正确。
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc(addFilters = false)
public class UserControllerTest {
@Autowired
MockMvc mockMvc;
@Autowired
ObjectMapper objectMapper;
@Test
public void whenRequestValid_thenReturnStatusOk() throws Exception{
User user = new User("John", "QPublic", "john.public@gmail.com",
"123456789", LocalDateTime.now(), LocalDateTime.now());
mockMvc.perform(MockMvcRequestBuilders.post("/api/user/register")
.content(objectMapper.writeValueAsString(user))
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
我正在 Spring Boot 中测试 @RestContoller
,它有一个 @PostMapping
方法,方法 @RequestBody
使用 @Valid
注释进行验证。为了测试它,我正在使用 MockMvc
并且为了填充请求正文内容我正在使用 Jackson ObjectMapper
;然而,当模型通过时,测试失败:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /api/user/register
Parameters = {}
Headers = [Content-Type:"application/json"]
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = com.springboottutorial.todoapp.controller.UserController
Method = public org.springframework.http.ResponseEntity<java.lang.String> com.springboottutorial.todoapp.controller.UserController.register(com.springboottutorial.todoapp.dao.model.User)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.http.converter.HttpMessageNotReadableException
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 = []
java.lang.AssertionError: Status
Expected :200
Actual :400
用户模型:
@Entity
@Table(name = "users",
uniqueConstraints = @UniqueConstraint(columnNames = {"EMAIL"}))
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private long id;
@Column(name = "FIRST_NAME")
@NotNull
private String firstName;
@Column(name = "LAST_NAME")
@NotNull
private String lastName;
@Column(name = "EMAIL")
@NotNull
@Email
private String emailAddress;
@Column(name = "PASSWORD")
@NotNull
private String password;
@Column(name = "CREATED_AT")
@NotNull
@Convert(converter = LocalDateTimeConveter.class)
private LocalDateTime createdAt;
@Column(name = "UPDATED_AT")
@NotNull
@Convert(converter = LocalDateTimeConveter.class)
private LocalDateTime updatedAt;
public User(@NotNull String firstName, @NotNull String lastName,
@NotNull @Email String emailAddress, @NotNull String password,
@NotNull LocalDateTime createdAt, @NotNull LocalDateTime updatedAt) {
this.firstName = firstName;
this.lastName = lastName;
this.emailAddress = emailAddress;
this.password = password;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
//setters and getters: omitted
用户控制器:
@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
UserService userService;
@PostMapping("/register")
public ResponseEntity<String> register(@RequestBody @Valid User user){
userService.createUser(user);
return ResponseEntity.ok().build();
}
}
用户控制器测试:
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc(addFilters = false)
public class UserControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void whenRequestValid_thenReturnStatusOk() throws Exception{
User user = new User("John", "QPublic", "john.public@gmail.com",
"123456789", LocalDateTime.now(), LocalDateTime.now());
mockMvc.perform(MockMvcRequestBuilders.post("/api/user/register")
.content(new ObjectMapper().writeValueAsString(user))
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
当我手动构建 JSON 字符串时,测试通过:
String json = "{\n" +
"\t\"firstName\" : \"John\",\n" +
"\t\"lastName\" : \"QPublic\",\n" +
"\t\"password\" : \"123456789\",\n" +
"\t\"createdAt\" : \"2016-11-09T11:44:44.797\",\n" +
"\t\"updatedAt\" : \"2016-11-09T11:44:44.797\",\n" +
"\t\"emailAddress\" : \"john.public@gmail.com\"\n" +
"}";
mockMvc.perform(MockMvcRequestBuilders.post("/api/user/register")
.content(json)
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(MockMvcResultMatchers.status().isOk());
这就是我实现其余控制器测试的方式。或许能帮到你。
我有这个摘要 class 来封装关于 JSON 映射的常见测试功能。
import lombok.SneakyThrows;
import lombok.val;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.http.MockHttpOutputMessage;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import static org.junit.Assert.assertNotNull;
public abstract class RestControllerTest {
private final MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(),
StandardCharsets.UTF_8);
private HttpMessageConverter messageConverter;
protected MediaType getContentType() {
return contentType;
}
@Autowired
void setConverters(HttpMessageConverter<?>[] converters) {
messageConverter = Arrays.stream(converters)
.filter(hmc -> hmc instanceof MappingJackson2HttpMessageConverter)
.findAny()
.orElse(null);
assertNotNull("the JSON message converter must not be null",
messageConverter);
}
@SneakyThrows
protected String toJson(Object data) {
val mockHttpOutputMessage = new MockHttpOutputMessage();
messageConverter.write(
data, MediaType.APPLICATION_JSON, mockHttpOutputMessage);
return mockHttpOutputMessage.getBodyAsString();
}
}
您可以像这样在测试中使用它class
@WebMvcTest(UserController.class)
@AutoConfigureMockMvc(addFilters = false)
public class UserControllerTest extends RestControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void whenRequestValid_thenReturnStatusOk() throws Exception{
User user = new User("John", "QPublic", "john.public@gmail.com",
"123456789", LocalDateTime.now(), LocalDateTime.now());
mockMvc.perform(MockMvcRequestBuilders.post("/api/user/register")
.content(toJson(user))
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
希望对你有用
正如 chrylis 在评论中提到的,问题的发生是由于 Java 8 日期和时间 API 和 Jackson 序列化冲突。默认情况下,ObjectMapper
不理解 LocalDateTime
数据类型,所以我需要向我的 maven 添加 com.fasterxml.jackson.datatype:jackson-datatype-jsr310
依赖项,这是一个数据类型模块,让 Jackson 识别 Java 8日期和时间 API 数据类型。
如果LocalDateTime
不是问题,我认为我们应该在用户class中实现equals
。
Spring 不一定为您提供 "vanilla" ObjectMapper 实例。通过让 Spring 将 ObjectMapper 实例注入测试而不是使用默认构造函数创建 ObjectMapper,您将获得一个与您的实际 运行 时间环境相匹配的实例,前提是您的 spring您的单元测试配置文件设置正确。
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc(addFilters = false)
public class UserControllerTest {
@Autowired
MockMvc mockMvc;
@Autowired
ObjectMapper objectMapper;
@Test
public void whenRequestValid_thenReturnStatusOk() throws Exception{
User user = new User("John", "QPublic", "john.public@gmail.com",
"123456789", LocalDateTime.now(), LocalDateTime.now());
mockMvc.perform(MockMvcRequestBuilders.post("/api/user/register")
.content(objectMapper.writeValueAsString(user))
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(MockMvcResultMatchers.status().isOk());
}
}