有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java Spring启动测试:当使用Jackson ObjectMapper将模型转换为JSON字符串时,UserControllerTest失败

我正在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());

共 (4) 个答案

  1. # 1 楼答案

    这就是我实现rest控制器测试的方式。也许它能帮助你

    我使用这个抽象类来封装有关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();
            }
        }
    

    你可以像这样在你的测试课上使用它

        @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());
            }
        }
    

    我希望它对你有用

  2. # 2 楼答案

    Spring不一定为您提供“普通”的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());
        }
    }
    
  3. # 3 楼答案

    正如chrylis在评论中提到的,问题是由于Java8Date&;时间API和Jackson序列化冲突。默认情况下,ObjectMapper不理解LocalDateTime数据类型,因此我需要将com.fasterxml.jackson.datatype:jackson-datatype-jsr310依赖项添加到我的maven中,这是一个数据类型模块,使Jackson能够识别Java8日期&;时间API数据类型。Astackoverflow question和Ablog post帮助我弄清楚我的问题到底是什么

  4. # 4 楼答案

    如果LocalDateTime不是问题,我认为我们应该在用户类中实现equals