简体   繁体   中英

How to mock RequestEntity.put() and restTemplate.exchange() in Spock groovy

Java code for api call

I want to know how to test the below two lines of code.

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

This is not a Spock question as such (you didn't present a single line of Spock specification, so nobody knows what you have tried so far), rather a software engineering or general testing question. The problem with testing spaghetti code - and here I mean code which both does something and creates lots of objects at the same time - is that from the outside there is no access to objects created inside a method and stored in a local variable. There are two ways to refactor your code for better testability:

  • If it makes sense, change the code so as to enable the user to inject the dependencies from the outside instead of the code creating them internally. Please google "dependency injection" and find variants like

    • constructor injection,
    • setter injection,
    • method parameter injection,
    • field injection when using tools/paradigms like CDI.
  • Another way is to factor out the object-creating parts of the method into smaller producer (or creator or factory) methods which then you can overwrite (stub) according to your choice in your test when using a partial mock (spy) object. Spock provides such spies, so you can easily use them.

I am going to show the latter approach for your information:

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

See how the method is more structured and more readable now (could still be improved, I just did it in a quick & dirty way)? Please especially notice the helper methods createRequestEntity and executeRequest .

Now here is how you can write your Spock test:

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"
  }
}

Feel free to ask (related.) follow-up questions if you do not understand this code, I advise you to learn more about clean code, testability. mock testing in general and also about Spock in particular.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM