【问题标题】:How to test if JSON path does not include a specific element, or if the element is present it is null?如何测试 JSON 路径是否不包含特定元素,或者该元素是否存在则为空?
【发布时间】:2015-11-30 14:10:27
【问题描述】:

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

java.lang.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().exists() 得到类似的异常,指出路径不存在。我正在分享更多代码 sn-ps,以便整个情况变得更具可读性。

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

@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 hatos 资源组装器将实体转换为资源对象,这是我的资源类:

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: check that key exists but value may be 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() 是我要找的。​​span>

标签: java unit-testing spring-mvc jsonpath mockmvc


【解决方案1】:

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

如果测试应该失败只有当有一个非null值时,使用:

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

如果属性一出现测试就失败了,即使是 null 值,使用:

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

【讨论】:

    【解决方案2】:

    我想重用我用来测试提供的参数的相同代码,因为它丢失了,这就是我想出的

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

    【讨论】:

      【解决方案3】:

      doesNotHaveJsonPath 用于检查它是否不在 json 正文中

      【讨论】:

        【解决方案4】:

        我在更新版本时遇到了同样的问题。在我看来,doesNotExist() 函数将验证密钥不在结果中:

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

        【讨论】:

        • 我就是这么做的,看看我问题的最后一行。 [问题最后编辑于 2015 年 9 月 4 日 14:55]
        • 如果你使用 AssertJ(例如在 Spring Boot 应用程序中),这是检查它的方法assertThat(this.json.write(entity)).doesNotHaveJsonPathValue("@.keyl");
        • 另外,要检查json中是否不存在任何属性(即json为空对象:{}),可以使用.andExpect(jsonPath("$.*").doesNotExist())
        【解决方案5】:

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

        行:

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

        甚至是属性为空的测试:

        .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 返回一个密码为空的用户,你可以在这个方法上编写一个普通的单元测试。

        附:当然密码是在数据库上加密的,对吧? ;)

        【讨论】:

        • 是的,JsonIgnore 正在按预期执行。那么在某些领域使用 JsonIgnore 测试实体的最佳实践是什么。假设这一点,假设您想编写一个测试,您知道哪些字段应该为空或 null(或者甚至不应该存在),但是您不会打开源代码并阅读它们是否实际上有 JsonIgnore 注释。显然你希望你的测试通过,而不是因为异常而失败。哦,当然密码是散列的。没关系,只是一个测试项目。
        • 您要做的是注意对象 UserResource 没有返回密码,对吧?假设您有 new UserResourceAsm().toResource(user) 返回用户,对吗?在这种情况下,您不应该在 SpringMVC 级别进行测试,而只是执行一个正常的单元测试来检查 user.getPassword() 是否为空。希望这澄清!
        • 我添加了一个示例来帮助您理解我的意思。让我看看你是否还有更多疑问
        • 是的!我问这个的原因是因为我在我遵循的教程中看到了类似的东西。但是教程作者使用了旧版本的 hamcrest / mockito。这可能是他设法获得测试成功的原因吗?
        • 我已经测试过了,我认为我对版本是正确的。使用 json-path 和 json-path-assert 版本 0.9.1,我的测试通过了。即使该字段不存在,对 null 的测试也会成功。但是,对于较新的版本,我认为您在回答中关于使用 Mapper 的描述是首选方法。我只是在学习单元测试的技巧。
        猜你喜欢
        • 1970-01-01
        • 2010-11-13
        • 2021-03-04
        • 2013-07-31
        相关资源
        最近更新 更多