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


77

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

java.lang.AssertionError:JSON路径:$ .password没有值,异常:路径:$ ['password']没有结果

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

@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()。exists(),它得到了类似的异常,指出该路径不存在。我将分享更多的代码片段,以便使整个情况更具可读性。

我正在测试的控制器方法看起来像这样:

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

我知道为什么会出现异常,并且在某种程度上也成功地测试到找不到密码字段。但是我要做的是,运行此测试以确保该字段不存在,或者如果存在,则该字段包含空值。我该如何实现?

堆栈溢出中有一个类似的帖子: 使用MockMvc的Hamcrest:检查键是否存在,但值可以为null

就我而言,该字段也可能不存在。

作为记录,这些是我使用的测试软件包的版本:

    <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路径匹配器的文档后,找到了一个与最新版本的jayway.jasonpath一起使用的快速修复程序。

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

感谢您的上一次“编辑”。这.doesNotExist()就是我想要的。
詹姆斯,

Answers:


159

我在较新版本中遇到了相同的问题。在我看来,dosNotExist()函数将验证密钥是否不在结果中:

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

1
这就是我所做的,请看我问题的最后一行。[问题最后编辑于
2015

1
如果您使用AssertJ(例如在Spring Boot应用程序中),这是检查它的方法assertThat(this.json.write(entity)).doesNotHaveJsonPathValue("@.keyl");
Adam Boczek

此外,要检查json中是否没有属性(换句话说,json是一个空对象{}.andExpect(jsonPath("$.*").doesNotExist())
:)

3

@JsonIgnore的行为符合预期,没有在json输出中生成密码,那么您如何期望测试您明确从输出中排除的内容?

该行:

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

甚至测试该属性是否为null:

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

对应于json,例如:

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

其中最重要的部分是左侧,即“属性”的存在:

相反,@ JsonIgnore根本不会在输出中生成所有内容,因此您不能指望它不在测试或生产输出中出现。如果您不希望该属性出现在输出中,那很好,但是您不能期望它在测试中。如果希望输出(在prod和test中)为空,则要在中间创建一个静态Mapper方法,该方法不会将属性的值传递给json对象:

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

然后您的方法将是:

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

此时,如果期望Mapper.mapPersonToRest返回具有空密码的用户,则可以在此方法上编写普通的单元测试。

PS当然密码是在数据库上加密的,对吗?;)


是的,JsonIgnore的表现符合预期。因此,在某些字段上使用JsonIgnore测试实体的最佳实践是什么。假设您要编写一个测试,知道哪些字段应该为空或为空(甚至不存在),但是您不会打开源代码并阅读是否在它们上面确实有JsonIgnore注释。很明显,您希望您的测试通过,并且不会因异常而失败。哦,当然,密码是哈希值。没关系,只是一个测试项目。
Zobayer Hasan 2015年

您想要做的是确保对象UserResource不返回密码,对吗?假设您有新的UserResourceAsm()。toResource(user)返回用户,对吗?在这种情况下,您不应该在SpringMVC级别进行测试,而应该执行常规的单元测试来检查user.getPassword()是否为null。希望这个澄清!
Paolof76 2015年

我添加了一个示例来帮助您理解我的意思。让我看看您是否还有更多疑问
Paolof76'9

是的,它确实!我之所以这样问,是因为我在遵循的教程中看到了类似的内容。但是教程作者使用了较旧的hamcrest / mockito版本。这可能是他设法获得考试成功的原因吗?
Zobayer Hasan 2015年

我已经测试过,并且我认为该版本是正确的。使用json-path和json-path-assert 0.9.1版,我的测试通过了。即使该字段不存在,测试null也会成功。但是,对于较新的版本,我认为您在回答中对使用Mapper的描述是首选的方法。我只是在学习单元测试的绳索。
Zobayer Hasan 2015年


0

我想重用我用于测试所提供参数的相同代码,因为它缺少参数,这就是我想到的

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

0

存在但具有null价值的属性与根本不存在的属性之间存在差异。

如果在存在非null值时测试应失败,请使用:

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

如果该属性存在后,即使有null值,测试也应立即失败,请使用:

.andExpect(jsonPath("password").doesNotHaveJsonPath())
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.