【问题标题】:How to mock RequestEntity.put() and restTemplate.exchange() in Spock groovy如何在 Spock groovy 中模拟 RequestEntity.put() 和 restTemplate.exchange()
【发布时间】:2019-09-25 15:03:20
【问题描述】:

用于 API 调用的 Java 代码

我想知道如何测试下面两行代码。

private void api(){
    //Code to call an API and i want to test this in groovy spock
    HttpHeaders httpHeaders = new HttpHeaders();      
    httpHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON); 
    httpHeaders.setContentType(MediaType.APPLICATION_JSON);
    RestTemplate restTemplate = new RestTemplate();
    String url ="url";
    String body ="body";
    //How to mock below line
    RequestEntity<String> requestEntity = RequestEntity.put(new URI(url)).contentType(MediaType.APPLICATION_JSON).body(body);
    //And this line
    ResponseEntity<String> responseEntity = restTemplate.exchange(requestEntity,String.class);
    HttpStatus StatusCode = responseEntity.getStatusCode();
}

【问题讨论】:

    标签: java spring testing groovy spock


    【解决方案1】:

    这不是一个 Spock 问题本身(您没有提供一条 Spock 规范,所以到目前为止没人知道您尝试了什么),而是一个软件工程或一般测试问题。测试意大利面条代码的问题——这里我的意思是同时做某事并创建许多对象的代码——是从外部无法访问在方法内部创建并存储在局部变量中的对象。有两种方法可以重构代码以获得更好的可测试性:

    • 如果有意义,请更改代码以使用户能够从外部注入依赖项,而不是在内部创建它们的代码。请谷歌“依赖注入”并找到类似的变体

      • 构造函数注入,
      • setter 注入,
      • 方法参数注入,
      • 使用 CDI 等工具/范式时的字段注入。
    • 另一种方法是将方法的对象创建部分分解为较小的生产者(或创建者或工厂)方法,然后您可以在使用部分模拟时根据您在测试中的选择覆盖(存根) (间谍)对象。 Spock 提供了这样的间谍,因此您可以轻松使用它们。

    我将展示后一种方法供您参考:

    package de.scrum_master.stackoverflow.q58101434;
    
    import org.springframework.http.*;
    import org.springframework.web.client.RestTemplate;
    
    import java.net.URI;
    import java.net.URISyntaxException;
    import java.util.Arrays;
    
    public class MyRestApi {
      public HttpStatus api() throws URISyntaxException {
        //Code to call an API and i want to test this in groovy spock
        HttpHeaders httpHeaders = createHttpHeaders();
        RestTemplate restTemplate = createRestTemplate();
        String url ="url";
        String body ="body";
        //How to mock below line
        RequestEntity<String> requestEntity = createRequestEntity(url, body);
        //And this line
        ResponseEntity<String> responseEntity = executeRequest(restTemplate, requestEntity);
        return responseEntity.getStatusCode();
      }
    
      HttpHeaders createHttpHeaders() {
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        return httpHeaders;
      }
    
      RestTemplate createRestTemplate() {
        return new RestTemplate();
      }
    
      RequestEntity<String> createRequestEntity(String url, String body) throws URISyntaxException {
        return RequestEntity.put(new URI(url)).contentType(MediaType.APPLICATION_JSON).body(body);
      }
    
      ResponseEntity<String> executeRequest(RestTemplate restTemplate, RequestEntity<String> requestEntity) {
        return restTemplate.exchange(requestEntity,String.class);
      }
    }
    

    看看这个方法现在如何更有条理和更易读(仍然可以改进,我只是以一种快速而肮脏的方式做到了)?请特别注意帮助方法createRequestEntityexecuteRequest

    下面是编写 Spock 测试的方法:

    package de.scrum_master.stackoverflow.q58101434
    
    import org.springframework.http.HttpStatus
    import org.springframework.http.RequestEntity
    import org.springframework.http.ResponseEntity
    import spock.lang.Specification
    import spock.lang.Unroll
    
    class MyRestApiTest extends Specification {
      @Unroll
      def "API returns status code #statusCode"() {
        given: "prepare mocks + spy"
        RequestEntity<String> requestEntity = Mock()
        ResponseEntity<String> responseEntity = Mock() {
          getStatusCode() >> httpStatus
        }
        MyRestApi myRestApi = Spy() {
          createRequestEntity(_, _) >> requestEntity
          executeRequest(_, _) >> responseEntity
        }
    
        when: "execute API method"
        def result = myRestApi.api()
    
        then: "check expected results"
        // This actually only tests mockfunctionality, your real test would look differently
        statusCode == result.value()
        reasonPhrase == result.reasonPhrase
    
        where:
        httpStatus                   | statusCode | reasonPhrase
        HttpStatus.OK                | 200        | "OK"
        HttpStatus.MOVED_PERMANENTLY | 301        | "Moved Permanently"
        HttpStatus.UNAUTHORIZED      | 401        | "Unauthorized"
      }
    }
    

    如果您不理解此代码,请随时提出(相关!)后续问题。我建议您了解更多关于整洁代码、可测试性、一般模拟测试以及特别是关于 Spock 的知识。

    【讨论】:

    • 1.我不明白间谍部分,您能否解释一下我们为什么要使用它 2. 我必须再次测试 createRequestEntity() 并且我在测试 createRequestEntity() 中的那一行时遇到了麻烦。感谢您的建议
    • 请定义“测试”。您可以只编写另一个测试,它不会存根您要测试的方法,而是真正调用它。或者您是在谈论验证静态RequestEntity.put(URI) 方法是否实际被调用?或者让它返回一个模拟RequestEntity.BodyBuilder?对于后两者,您将需要像 PowerMockito 这样的附加组件,因为 Spock 仅支持 Groovy 类的存根和验证静态方法,而不支持 Java 类。请清楚地表达自己或让代码说话。我不喜欢猜测。你想达到什么目标?
    • 间谍是部分模拟,请完成您的开发者作业并阅读Spock documentation(不仅仅是本章),以了解如何使用您选择的工具。我无法从头开始向你解释一切。基本上,spy 是一个特殊的 mock,它包装了原始对象并通过方法调用传递它,除了您在测试设置中选择存根(覆盖)的方法。
    • 我说的是验证实际调用的静态方法RequestEntity.Put(URL)。感谢您的参考,我将通过它:)
    • 让我挑战您的意图:您为什么要验证是否调用了外部方法?有什么好处?通过将其与方法的内部实现联系起来,您只会使您的测试变得脆弱。如果您说您想用模拟对象替换外部方法调用的结果,我宁愿理解,因为方法调用可能很昂贵(例如打开文件或数据库连接或进行慢速查询)。然后将结果存根以仅返回一个模拟将有助于您的测试运行得更快。但验证对 IMO 没有明显的好处。
    猜你喜欢
    • 1970-01-01
    • 2018-03-31
    • 2020-05-23
    • 1970-01-01
    • 1970-01-01
    • 2021-11-26
    • 2020-12-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多