尝试将带有@JsonProperty批注的实体发布到@RequestBody上带有@Valid批注的端点时,控制器测试失败

时间:2018-12-21 11:13:44

标签: java jackson mockito spring-restcontroller mockmvc

我在测试其余控制器端点时遇到问题。 我正在尝试将实体POST到在@RequestBody字段上用@Valid注释的端点。实体字段使用验证规则和杰克逊注释进行注释。

我正在尝试使用单元测试来测试POST端点。

带有注释的实体和字段

public class User {
    //other fields 

    @Column(name = "PASSWORD", nullable = false)
    @NotEmpty(message = "Users password must be filled.")
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    private String userPassword;
}

控制器

@RestController
@RequestMapping(value = "/api/users", produces = "application/hal+json")
public class UserController {

    @PostMapping("")
    public ResponseEntity<User> addNewUser(@Valid @RequestBody User newUser) {
        User createdUser = userService.saveUserInDatabase(newUser);
        return new ResponseEntity<>(createdUser, HttpStatus.OK);
    }
}

和单元测试

@RunWith(MockitoJUnitRunner.class)
public class UserControllerTest {
    //other tests 
    @Test
    public void shouldBeAbleToAddNewUser() throws Exception {
        User newUser = new User();
        newUser.setUserName("userName");
        newUser.setUserEmail("userName@domain.com");
        newUser.setUserPassword("secret1");
        newUser.setEnable(true);

        User createdUser = new User(1L, "userName", "userName@domain.com", "secret1", true);

        when(userService.saveUserInDatabase(any(User.class))).thenReturn(createdUser);

        mockMvc.perform(post("/api/users")
                    .contentType(MediaTypes.HAL_JSON_UTF8)
                    .content(jsonUser.write(newUser).getJson()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.userName", Matchers.equalToIgnoringCase(createdUser.getUserName())))
                .andExpect(jsonPath("$.userId", Matchers.is(createdUser.getUserId().intValue())))
                .andExpect(jsonPath("$.userPassword").doesNotExist());
    }
}

重要的是。挥舞地获取(GET)实体时,字段“ userPassword”未序列化。由于@JsonProperty批注,它可以按预期工作。当实体用招摇过帐也可以正常工作时,密码将保存在数据库中,并且响应中不包含“ userPassword”字段。另外,如果缺少任何必填字段(用@NotEmpty注释),则会引发异常。

正在测试获取数据的测试,可以正常工作,'userPassword'在json中不可见响应

andExpect(jsonPath("$.userPassword").doesNotExist())

但是执行附加测试时,它以错误的状态(400而不是200)失败,并显示信息“必须填写用户密码”。 密码已填写,但看起来好像没有推送到请求正文。我进行了一些调试,并注意到将@JsonProperty添加到“ userPassword”字段时,该字段设置为“ null”。

// edit
以此方式定义了用于以JSON格式发布新用户数据的jsonUser

@RunWith(MockitoJUnitRunner.class)
public class UserControllerTest {

//other variables and methods

private JacksonTester<User> jsonUser;

    @Before
    public void setup() {
        JacksonTester.initFields(this, new ObjectMapper());
        mockMvc = MockMvcBuilders.standaloneSetup(userController).build();
    }
}

// edit
我已经找到解决问题的方法。 jsonUser.write 必须首先从newUser中读取数据(很明显),并包括json批注。由于getter被jsonproperty阻止,因此在读取过程中将其省略。这就是为什么测试用例无法正常工作,但是通过大摇大摆的POST可以正常工作的原因。 为了解决这个问题,我准备了包含json格式数据的String

String userDetails = "{\"userName\":\"userName\",\"userEmail\":\"userName@domain.com\",\"userPassword\":\"secret1\",\"enable\":true}";

mockMvc.perform(post("/api/users")
                    .contentType(MediaTypes.HAL_JSON_UTF8)
                    .content(userDetails)) 
                    .andExpect(status().isOk());

1 个答案:

答案 0 :(得分:0)

您使用

设置帖子的内容
jsonUser.write(newUser).getJson()

jsonUser是JacksonTester,并被赋予了Jackson ObjectMapper以执行序列化/反序列化。当您使用write方法来序列化用户对象时,它将遵循

@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)

可能会混淆使用“写入”。 JacksonTester上的方法是write(),这意味着序列化(将对象写入json),就像JsonProperty.Access.WRITE_ONLY意味着仅在反序列化(从json写入对象)期间使用此字段。

您获得的Json将不包含任何密码详细信息。即您得到类似的东西:

{"userName":"userName","userEmail":"userName@domain.com","enable":true}

您可以将其用作测试POST请求的内容。当杰克逊从POST请求的内容中反序列化您的json时,它将找不到密码。这就是为什么您看到自己看到的东西。

我了解您为什么在类上使用WRITE_ONLY选项,防止在序列化密码时将其写出很有意义。但是,这意味着您不能使用该类通过简单地序列化来创建要发送到服务器的json。

一种方法是用一个仅有密码字段的TestUser来为您的User子类化。然后在您的JacksonTester中使用TestUser。

public class TestUser extends User {
    private String userPassword;
}

然后在测试类中,将User替换为TestUser

private JacksonTester<TestUser> jsonUser;

    TestUser newUser = new TestUser();
    newUser.setUserName("userName");
    newUser.setUserEmail("userName@domain.com");
    newUser.setUserPassword("secret1");
    newUser.setEnable(true);