Reputation: 13666
I'm using RestTemplate to call a rest controller from a controller, e.g.:
@RequestMapping(method = POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@ResponseBody
public String formForUpload(@RequestParam("file") MultipartFile file) throws Exception {
final MultiValueMap<String, Object> parts=...;
restTemplate.postForObject("http://localhost:8080/rest/something",
parts, MultipartFile.class);
...
}
How can I unit test that the URL called by the controller is correct. Is there any way to get the path for the rest controller (e.g. using such reflection technique)?
I don't want to run an integration test!
Upvotes: 3
Views: 6088
Reputation: 17713
Maybe this is not the correct answer, but maybe it helps.
Assuming that the rest controller (let say MyRestController
) has the class declaration annotated with @RequestMapping
, then I assume that you can simply get the currently defined path as follows:
final String controllerPath = MyRestController.class
.getAnnotation(RequestMapping.class).value()[0];
UPDATE
Why don't you try the following changes to your code. They are based on Spring, JUnit and Mockito. Assuming your controller is class MyController
.
@Test
public void testUploadPostCallsToRestService() throws Exception {
final MockMultipartFile file = //create a mock for a file to be uploaded;
final MyController myController = new MyController();
final RestTemplate restTemplate = new RestTemplate();
final MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);
ReflectionTestUtils.setField(myController, "restTemplate", restTemplate); //I assume you use Spring MVC from your tagging, isn't it?
final MockMvc mockMvc = MockMvcBuilders.standaloneSetup(myController).build();
mockServer.expect(requestTo("http://localhost:8080/rest/something"))
.andExpect(method(POST))
.andRespond(withStatus(OK)); //Or whatever your rest returns
mockMvc.perform(fileUpload("http://localhost:8080/path/to/my/controller")
.file(file)
.accept(MediaType.MULTIPART_FORM_DATA))
.andExpect(status().isOk()); //I hope you return this :)
mockServer.verify();
}
This works very well for me. Essentially, I use the mock for the rest service server that has to serve the RestTemplate
. So, the latter will call the mock that behaves as you specified. Then, it is injected in your controller using reflection. Finally, I use MockMvc to upload the file and get the answer from the controller.
Upvotes: 4
Reputation: 2862
Is method argument value capturing what you're looking for?
You can use a mocking framework to mock the rest template and capture the method invocation arguments.
I suggest to use TestNG and JMockit.
Controller class (the example is working code, so I want to show the whole controller class):
import org.springframework.beans.factory.annotation.*;
import org.springframework.http.MediaType;
import org.springframework.util.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
public class MyController {
@Autowired
@Qualifier("MyRestTemplate")
private RestTemplate restTemplate;
@RequestMapping(method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@ResponseBody
public String formForUpload(@RequestParam("file") MultipartFile file) {
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
restTemplate.postForObject("http://localhost:8080/rest/something", parts, MultipartFile.class);
return "[{\"good-data\"}]";
}
}
The unit test:
import mockit.*;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import org.testng.Assert;
import org.testng.annotations.Test;
import java.util.ArrayList;
public class MyControllerTest {
@Test
public void testFormForUpload(@Mocked final RestTemplate restTemplate) {
MyController controller = new MyController();
Deencapsulation.setField(controller, restTemplate);
final ArrayList<String> urlValueHolder = new ArrayList<>();
new Expectations() {{
restTemplate.postForObject(withCapture(urlValueHolder), any, MultipartFile.class);
}};
controller.formForUpload(new MockMultipartFile("data-file.txt", "various data".getBytes()));
Assert.assertEquals(urlValueHolder.size(), 1);
Assert.assertEquals(urlValueHolder.get(0), "http://localhost:8080/rest/something");
}
}
Notes:
RestTemplate
mock is passed as an argument of the test method. That's all you need to do to create a working mock.withCapture
call that I use as the first argument of RestTemplate#postForObject
. The url(s) used by the controller will be stored in the list urlValueHolder
.I believe the test is easy enough, but still I would recommend to refactor the controller. Instead of using mocks and testing controllers to verify the urls, I would suggest to move the url creation to a separate factory class. A simple unit test of the factory would be all that's needed to test the urls.
Upvotes: 1
Reputation: 1283
There is a class for generating links to controllers and controller methods in the Spring Hateoas project. Specifically have a look at org.springframework.hateoas.mvc.ControllerLinkBuilder
. It will be able to generate URL's for simple @RequestMapping
annotations, but if you use multiple mappings on the same handler method it won't be able to figure out which one to generate.
Also, if your controller handler methods return String
, the link builder will throw an error, so change the return type to Object
and it'll work.
Upvotes: 0