如何测试 JSON 路径是否包含特定的元素,或者如果该元素存在,它是否为 null?

我一直在为一个简单的 Spring Web 应用程序编写一些简单的单元测试例程。当我在资源的 getter 方法上添加@JsonIgnore 注释时,生成的 json 对象不包含相应的 json 元素。因此,当我的单元测试例程试图测试这是否为 null (这是我的例子所期望的行为,我不希望密码在 json 对象中可用)时,测试例程会遇到一个异常:

AssertionError: 没有 JSON 路径的值: $. password,异常: 没有路径的结果: $[‘ password’]

这是我编写的单元测试方法,用 is (nullValue ())方法测试“ password”字段:

@Test
public void getUserThatExists() throws Exception {
User user = new User();
user.setId(1L);
user.setUsername("zobayer");
user.setPassword("123456");


when(userService.getUserById(1L)).thenReturn(user);


mockMvc.perform(get("/users/1"))
.andExpect(jsonPath("$.username", is(user.getUsername())))
.andExpect(jsonPath("$.password", is(nullValue())))
.andExpect(jsonPath("$.links[*].href", hasItem(endsWith("/users/1"))))
.andExpect(status().isOk())
.andDo(print());
}

我还用 jsonPath ()尝试过这种方法。得到类似的异常,说明路径不存在。我正在分享更多的代码片段,以便整个情况变得更具可读性。

我正在测试的控制器方法如下:

@RequestMapping(value="/users/{userId}", method= RequestMethod.GET)
public ResponseEntity<UserResource> getUser(@PathVariable Long userId) {
logger.info("Request arrived for getUser() with params {}", userId);
User user = userService.getUserById(userId);
if(user != null) {
UserResource userResource = new UserResourceAsm().toResource(user);
return new ResponseEntity<>(userResource, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}

我使用 spring hateos 资源汇编程序将实体转换为资源对象,这是我的资源类:

public class UserResource extends ResourceSupport {
private Long userId;
private String username;
private String password;


public Long getUserId() {
return userId;
}


public void setUserId(Long userId) {
this.userId = userId;
}


public String getUsername() {
return username;
}


public void setUsername(String username) {
this.username = username;
}


@JsonIgnore
public String getPassword() {
return password;
}


public void setPassword(String password) {
this.password = password;
}
}

我理解为什么会出现异常,在某种程度上,测试是成功的,因为它找不到密码字段。但是我想做的是,运行这个测试来确保字段不存在,或者如果存在,它包含空值。我怎么才能做到呢?

在堆栈溢出方面也有类似的帖子: Hamcrest with MockMvc: 检查键是否存在,但值可能为空

在我的情况下,这个领域可能也不存在。

请注意,下面是我使用的测试包的版本:

    <dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path-assert</artifactId>
<version>2.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>

先谢谢你。

[编辑] 更准确地说,您必须为一个实体编写一个测试,在这个实体中,您知道一些字段需要为 null 或空,或者甚至不应该存在,并且您实际上不需要遍历代码来查看在属性顶部是否添加了 JsonIgnore。你想让你的测试通过,我怎么能这么做。

请随时告诉我,这是不切实际的,在所有,但仍然是很高兴知道。

[编辑] 上面的测试通过以下旧的 json-path 依赖关系获得成功:

    <dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>0.9.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path-assert</artifactId>
<version>0.9.1</version>
<scope>test</scope>
</dependency>

[编辑]在阅读 Spring 的 json path matcher 文档后,找到了一个可以使用 jayway.jasonpath 最新版本的快速修复程序。

.andExpect(jsonPath("$.password").doesNotExist())
87375 次浏览

@JsonIgnore is behaving as expected, not producing the password in the json output, so how could you expect to test something that you are explicitly excluding from the output?

The line:

.andExpect(jsonPath("$.property", is("some value")));

or even a test that the property is null:

.andExpect(jsonPath("$.property").value(IsNull.nullValue()));

correspond to a json like:

{
...
"property": "some value",
...
}

where the important part is the left side, that is the existence of "property":

Instead, @JsonIgnore is not producing the porperty in the output at all, so you can't expect it not in the test nor in the production output. If you don't want the property in the output, it's fine, but you can't expect it in test. If you want it empty in output (both in prod and test) you want to create a static Mapper method in the middle that is not passing the value of the property to the json object:

Mapper.mapPersonToRest(User user) {//exclude the password}

and then your method would be:

@RequestMapping(value="/users/{userId}", method= RequestMethod.GET)
public ResponseEntity<UserResource> getUser(@PathVariable Long userId) {
logger.info("Request arrived for getUser() with params {}", userId);
User user = Mapper.mapPersonToRest(userService.getUserById(userId));
if(user != null) {
UserResource userResource = new UserResourceAsm().toResource(user);
return new ResponseEntity<>(userResource, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}

At this point, if your expectations are for Mapper.mapPersonToRest to return a user with a null password, you can write a normal Unit test on this method.

P.S. Of course the password is crypted on the DB, right? ;)

I had the same problem with the newer version. It looks to me that the doesNotExist() function will verify that the key is not in the result:

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

doesNotHaveJsonPath for checking that it is not in json body

I wanted to reuse the same code I use for testing for the parameter being supplied, and for it missing, and this is what I came up with

  @Test
void testEditionFoundInRequest() throws JsonProcessingException {
testEditionWithValue("myEdition");
}


@Test
void testEditionNotFoundInRequest() {
try {
testEditionWithValue(null);
throw new RuntimeException("Shouldn't pass");
} catch (AssertionError | JsonProcessingException e) {
var msg = e.getMessage();
assertTrue(msg.contains("No value at JSON path"));
}
}




void testEditionWithValue(String edition) {
var HOST ="fakeHost";
var restTemplate = new RestTemplate();
var myRestClientUsingRestTemplate = new MyRestClientUsingRestTemplate(HOST, restTemplate);
MockRestServiceServer mockServer;
ObjectMapper objectMapper = new ObjectMapper();
String id = "userId";
var mockResponse = "{}";


var request = new MyRequest.Builder(id).edition(null).build();
mockServer = MockRestServiceServer.bindTo(restTemplate).bufferContent().build();


mockServer
.expect(method(POST))


// THIS IS THE LINE I'd like to say "NOT" found
.andExpect(jsonPath("$.edition").value(edition))
.andRespond(withSuccess(mockResponse, APPLICATION_JSON));


var response = myRestClientUsingRestTemplate.makeRestCall(request);
} catch (AssertionError | JsonProcessingException e) {
var msg = e.getMessage();
assertTrue(msg.contains("No value at JSON path"));
}

There is a difference between the property that is present, but having null value, and the property not being present at all.

If the test should fail only when there is a non-null value, use:

.andExpect(jsonPath("password").doesNotExist())

If the test should fail as soon as the property is present, even with a null value, use:

.andExpect(jsonPath("password").doesNotHaveJsonPath())