How do I unit test a Spring RestTemplate that takes a ResponseExtractor and RequestCallback? - spring

I am developing in Groovy and I am trying to write a Spock unit test for the following use of Spring's RestTemplate...
Included are my request callback and response extractors, and my initialization class of the RestTemplate bean. I am using the ResponseExtractor to stream the response from GET myurl/ and copy it to a file. The RequestCallback is simply setting some headers on the request.
class RestTemplateConfig() {
#Bean(name = 'myRestTemplate')
RestTemplate getMyRestTemplate() {
RestTemplate restTemplate = new RestTemplateBuilder().build()
return restTemplate
}
}
class MyClass() {
#Autowired
#Qualifier('myRestTemplate')
RestTemplate restTemplate
File getFile() {
ResponseExtractor<Void> responseExtractor = { ClientHttpResponse response ->
// do something with the response
// in this case, the response is an input stream so we copy the input stream to a file
myFile = response.getBody() // roughly, in a psuedocode-ish way
return null
}
RequestCallback requestCallback = { ClientHttpRequest request ->
request.getHeaders().setAccept([MediaType.APPLICATION_JSON])
}
File myFile
// get my file data
restTemplate.execute('myurl/', HttpMethod.GET, requestCallback, responseExtractor)
return myFile
}
}
Spring framework docs for that particular execute(...) method: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html#execute-java.net.URI-org.springframework.http.HttpMethod-org.springframework.web.client.RequestCallback-org.springframework.web.client.ResponseExtractor-
How do I mock out what's happening in these closures? Specifically, I'm interested in mocking out my response extractor because my current test always returns myFile as null.
when:
// do stuff
then:
1 * restTemplate.execute('myurl/, HttpMethod.GET, _, _) // how can I mock out the expected response here?
0 * _
myFile != null // this fails because myFile is null

After you updated your sample code as I requested, I can see more clearly now. You are suffering from a typical (non-)testability problem: Your method getFile does more than just getting a file. It instantiates two dependencies as local variables, making them unmockable and consequently the whole method mostly untestable.
So you want to refactor for better testability so as to be able to use one testing method I mentioned in my first comment:
If the requestCallback and responseExtractor can be injected via constructor or setter, you can inject mocks.
If they are created by some kind of factory class, you can stub that class.
In case of a factory method inside the class under test itself you can use a spy on the class and stub the factory method.
For a more general discussion of testability and how tests drive application design, see my other answer here, sections "General comments" and "Update".
If any of this is unclear, feel free to ask related(!) follow-up questions.

Related

How to retrieve appended authorization header's access token from custom RestTemplate implementation?

I have a custom RestTemplate class that extends the RestTemplate class. Purpose of it is to append an access token to it for users to use in making API requests.
The class looks like this:
public class ModifiedRestTemplate extends RestTemplate {
private String token;
private void setInterceptorWithAuthorizationHeaders() {
super.getInterceptors().clear();
super.getInterceptors().add((request, body, execution) -> {
request.getHeaders().add("Authorization", "Bearer "+ this.token);
return execution.execute(request, body);
});
}
I want to unit test this class and check that the header within the custom RestTemplate indeed contains the mocked access token (this.token in case you're still confused on my goal). Reason being there is another method within this class that triggers the updating of said access token in the custom RestTemplate. So I would like to test that the updating process was successful. I'm not sure how to go about doing this.
RestTemplate API has a headForHeaders method but this method returns an empty header (expected, since my mockServer setup includes an empty success() block anyway).
Code sample below:
mockServer.expect(ExpectedCount.once(), requestTo("http://localhost:8080/test/mock")).andExpect(method(HttpMethod.HEAD)).andRespond(withSuccess());
HttpHeaders httpHeaders = modifiedRestTemplate.headForHeaders("http://localhost:8080/test/mock");
Is there a way to reach into the custom RestTemplate object/instance and extract the header with its access token?

How to write Mockito for Rest template 2 based response from RestTemplate 1

#Test
public void getEventsByOrg() throws Exception {
String mockResposne = getXMLFromFile("classpath:OrgResponse.xml");
ResponseEntity<String> response = new ResponseEntity<>(mockResposne, HttpStatus.OK);
when(restTemplate.exchange(any(String.class), any(), any(HttpEntity.class), any(Class.class)))
.thenReturn(response);
ResponseEntity<List<OCVEvents>> ocvEvents = eventService.getEventsByGlobalKey(eventIdOrg, traceId);
verify(restTemplate).exchange(any(String.class), any(), captor.capture(), any(Class.class));
Events event = ocvEvents.getBody().get(0);
Events eventsPerson = new ObjectMapper().readValue(ResourceUtils.getFile("classpath:EventOrg.json"), Events.class);
assertThat(event.getHeader()).isEqualTo(eventsPerson.getHeader());
Now i have another Rest Call inside eventsService
How to write unit test for that
I need to hit a Rest API(1) & get the response, based on response i need to hit another Rest API (2)
i need to write the mockit0 for this class
If you have multiple rest api calls being made in your service class , you will have to mock all the api calls to return a mock data for your test to run. Just like you mocked the response for the first api call, add a mock response for the second api call before calling your service in you test. Instead of using any() as the argument matcher specify the particular url that you are going to be calling from the code to differentiate between the two api call mocks.
If you are on Springboot and using spring-boot-test for Integration testing , then you could use TestRestTemplate like:
TestRestTemplate testRestTemplate = new TestRestTemplate();
ResponseEntity<String> response = testRestTemplate.
getForEntity(FOO_RESOURCE_URL + "/1", String.class);
assertThat(response.getStatusCode(), equalTo(HttpStatus.OK));
or If you are just having a Unit test case using Mockito for instance then :
#Mock
private RestTemplate restTemplate;
#InjectMocks
private EventService eventService = new EventService();
#Test
public void givenMockingIsDoneByMockito_whenGetIsCalled_shouldReturnMockedObject() {
SomeObject instance = new SomeObject(“E001”, "Eric Simmons");
Mockito
.when(restTemplate.getForEntity(
“http://localhost:8080/test/E001”, SomeObject.class))
.thenReturn(new ResponseEntity(instance, HttpStatus.OK));
SomeObject returnedObject = eventService.getEventsByGlobalKey(id);
Assert.assertEquals(instance, returnedObject);
}

How to test a POSTing method by using an embedded Webserver in Springboot?

I am searching for a way to test a method, which sends a POST request to an external service. The application will NOT be itself a consumable webservice, that is why I didn't implement the shown class below as #RestController, #Controller, #Service, whatever types there may be.
But I don't know how to call the method postNumberPlate() to send a request to an embedded webserver (started in/by/at the unit test) to make some assertions on it. I want to avoid, to install an external webserver.
In other words: Can I start an embedded webserver inside a unit-test and 'tell' it to accept my POST request to inspect and assert the contents?
I already did:
a massive Webresearch (2-3 days?)
read Howto's
check the springboot docs
use an embedded Jetty Server (somehow blocking loop)
declare the Application as Webapplication and setting random port to jetty
experiment with Mockito, MockMVC
read "How to unittest a class using RestTemplate offline?" and compared it to my case, but found,
that it's very old (8y),
I don't know how to implement the parent interface, which is pretty huge
that the question and answers are too generic to deduce a solution for my case
it's not answering the embedded testing webserver problem I included.
The Class to be tested:
public class RestfulClient {
private RestTemplate restTemplate = new RestTemplate();
private HttpHeaders headers = new HttpHeaders();
#Value("${kafkaeskadapter.uri}")
private String destinationURL;
public RestfulClient() {}
public ResponseEntity<String> postNumberPlate(String key, CamImage camImage) {
LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.add("numplate", camImage.getIdentifier());
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<LinkedMultiValueMap<String, Object>> requestEntity = new HttpEntity<LinkedMultiValueMap<String,
Object>>(map, headers);
ByteArrayResource resource = new ByteArrayResource(camImage.getData()) {
/**
* IMPORTANT!!! Otherwise I receive a BAD REQUEST
* #return
*/
#Override
public String getFilename() {
return camImage.getIdentifier() + ".png";
}
};
map.add("image", resource);
ResponseEntity<String> result = restTemplate.exchange(destinationURL, HttpMethod.POST,
requestEntity, String.class);
return result;
}
}
I hope I could clarify my question a bit.
A solution is to write a simple light-weight Webservice Endpoint and include it into your Run Configuration of your IDE. I made a separate mini project and would add further methods if needed, e.g. to accept different media.
Prior to run the actual unit tests, it is possible to configure the start of the Endpoint and return a meaningful ResponseEntity. The result can be inspected et voilà, assertions are possible.
A word about StackOverflow user arrogance: #Raedwald, after reading and trying, the answers in the linked question are not really helpful, but involve a lot of knowlegde about the stuff, and I have no one around of my colleagues, which could ever assist at programming. So it wasn't helpful to flag my question for deletion.

Cannot get Spring Boot to lazily resolve a multipart file

I have created a Spring Boot 2 demo application with the Spring Initializr and added the controller below:
#Controller
#RequestMapping("/demo")
public class UploadController {
private final static Logger LOG = LoggerFactory.getLogger(UploadController.class);
#PostMapping("/upload")
public ResponseEntity<String> uploadFile(
#RequestParam("metadata") MultipartFile metadata,
#RequestParam("payload") MultipartFile payload) throws IOException {
ObjectMapper mapper = new ObjectMapper();
Map metadataMap = mapper.readValue(metadata.getInputStream(), Map.class);
LOG.info("Received call to upload file {}", metadataMap.get("filename"));
LOG.info("File size: {}", payload.getBytes().length);
LOG.info("File {} successfully uploaded", metadataMap.get("filename"));
return ResponseEntity.ok().build();
}
}
I then added an application.yaml file containing this configuration:
spring:
servlet:
multipart:
max-file-size: 2000000MB
max-request-size: 2000000MB
resolve-lazily: true
My goal is to have the controller parse and log the metadata file before it starts reading the payload file, but the resolve-lazily setting seems to be ignored by Boot: the code inside the controller won't be executed until the whole body is read.
I use the command below to test the controller:
curl -F metadata=#metadata.json -F payload=#payload.bin http://localhost:8080/demo/upload
Is there anything wrong with my code/configuration? Am I getting the meaning of the setting right?
At present, if you want to avoid reading (and buffering) the whole body all at once, I think you will have to provide your own parser, as described in the answers here. What would be really interesting (but generally unnecessary) would be to do so in the form of a new MultipartResolver implementation.
There are two existing implementations documented for interface MultipartResolver, and both supply a function setResolveLazily(boolean) (standard), (commons). I have tried with both, and neither seem to allow for parsing or streaming multipart files or parameters independently.
Default is "false", resolving the multipart elements immediately, throwing corresponding exceptions at the time of the resolveMultipart(javax.servlet.http.HttpServletRequest) call. Switch this to "true" for lazy multipart parsing, throwing parse exceptions once the application attempts to obtain multipart files or parameters.
Despite what it says in the documentation, I have found that once you call resolveMultipart, the entire body is parsed and buffered before the call returns. I know this because I can watch the temp-files being created.
One note about "Is there anything wrong with my code"...
Answer: Yes, because by using #RequestParam you have indirectly asked Spring to resolve your parameters ahead of time, before your controller is ever called. What you should be able to do instead (if the documentation were correct) is request the parameters independently from inside your controller:
Configuration (application.properties):
spring.servlet.multipart.enabled = true
spring.servlet.multipart.resolve-lazily = true
Controller:
#PostMapping(path = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<Void> postUpload(HttpServletRequest rawRequest) {
multipartResolver.setResolveLazily(true); // unclear why this is exists
MultipartHttpServletRequest request = multipartResolver.resolveMultipart(rawRequest);
String p1 = request.getParameter("first-parameter");
String p2 = request.getParameter("second-parameter");
System.out.println("first-parameter="+p1+", second-parameter"+p2);
multipartResolver.cleanupMultipart(request);
return new ResponseEntity<Void>(HttpStatus.ACCEPTED);
}
One useful aspect of resolve-lazily that I have discovered is that it allows you to write your own parser for some rest controllers while using the built-in parser for others (see my answer here). In other words, you don't have to use spring.servlet.multipart.enabled = false to get your parser to work. This is a minor breakthrough relative to other advice that I had seen previously.

Why doesn't Spring MVC throw an error when you POST to a controller action that accepts HTTP GET?

I just noticed a weird problem as I've been testing my application. I was accidentally POSTing to a method that accepts HTTP GET (It was a typo - I'm a little tired), but the weird thing is that Spring was executing a GET action anyway - it wasn't throwing an error.
Here is the mapping for my GET action that I was POSTing to instead:
#RequestMapping(value = "/partialMapping/{partialMappingId}/edit", method = RequestMethod.GET)
public ModelAndView edit(#PathVariable long partialMappingId) {
return new ModelAndView(view("edit"), "partialMapping",
partialMappingService.findPartialMapping(partialMappingId));
}
What I would have expected was for Spring to say, "There is no action called /partialMapping/{partialMappingId}/edit for HTTP POST".
Instead... if you use the HandlerAdapter and pass it "POST" and "/partialMapping/1/edit", it runs my index action instead ("/partialMapping"). It doesn't throw an error. Why?
Is this a bug in spring, or is this desired behaviour? It's not a big deal when it comes to production code, but it surely makes debugging problems harder.
Here is the code I am using to execute a controller action in my tests:
protected ModelAndView handle(HttpServletRequest request, HttpServletResponse response) {
try {
final HandlerMapping handlerMapping = applicationContext.getBean(HandlerMapping.class);
final HandlerExecutionChain handler = handlerMapping.getHandler(request);
assertNotNull("No handler found for request, check you request mapping", handler);
final Object controller = handler.getHandler();
// if you want to override any injected attributes do it here
final HandlerInterceptor[] interceptors =
handlerMapping.getHandler(request).getInterceptors();
for (HandlerInterceptor interceptor : interceptors) {
final boolean carryOn = interceptor.preHandle(request, response, controller);
if (!carryOn) {
return null;
}
}
return handlerAdapter.handle(request, response, controller);
} catch(Exception e) {
throw new RuntimeException(e);
}
}
I found this code per another answer to a question on this site.
I believe your test code mimics the dispatch step that tries to find a matching Controller method signature after the URL and HTTP method have resolved. In other words, you are not testing your controller at the right level if you want to test the HTTP message bindings. For that kind of testing you would probably want to deploy to a server (perhaps embedded Jetty inside your test) and use RestTemplate to call it. That's what I do anyway.
If you annotate with Spring MVC annotations as below
#RequestMapping(method = RequestMethod.GET it should work.

Resources