I have a REST API outside of my control (supplied by a different, distant team) which I need to consume from a Spring Boot application.
Currently I would like to write a test for that the request (not response) resulting from my RestTemplate invocation corresponds exactly to what is expected at the remote end. I have a sample JSON snippet that I would like to replicate from my code - given the same parameters as in the sample I should get an equivalent JSON snippet in the request body which I would then like to analyze to be certain.
My idea so far is to get RestTemplate to use a server under my control which then captures the JSON request. Apparently MockRestServiceServer is a good choice for this.
Is this the right approach? How do I configure MockRestServiceServer to allow me to do this?
If you're only interested in verifying the JSON mapping, you can always use Jackson's ObjectMapper directly and verify if the object structures match by using a library like JSONassert to verify if the serialized string matches your expected result. For example:
#Autowired
private ObjectMapper objectMapper;
private Resource expectedResult = new ClassPathResource("expected.json");
#Test
public void jsonMatches() {
Foo requestBody = new Foo();
String json = objectMapper.writeValueAsString(requestBody);
String expectedJson = Files
.lines(expectedResult.getFile())
.collect(Collectors.joining());
JSONAssert.assertEquals(expectedJson, json, JSONCompareMode.LENIENT);
}
This test purely uses ObjectMapper to verify the JSON mapping and nothing else, so you could even do this without actually having to bootstrap Spring boot within your test (which could be faster). The downside of this is that if you're using a different framework than Jackson, or if RestTemplate changes its implementation, that this test could become obsolete.
Alternatively, if you're interesting in verifying that the complete request matches (both URL, request method, request body and so on), you can use MockRestServiceServer as you mentioned. This can be done by adding the #SpringBootTest annotation to your test, autowiring RestTemplate and the service that invokes RestTemplate for example:
#RunWith(SpringRunner.class)
#SpringBootTest
public class FooServiceTests {
#Autowired
private RestTemplate restTemplate;
#Autowired
private FooService fooService; // Your service
private MockRestServiceServer server;
#Before
public void setUp() {
server = MockRestServiceServer.bindTo(restTemplate).build();
}
}
You can then set up your tests by using:
#Test
public void postUsesRestTemplate() throws IOException, URISyntaxException {
Path resource = Paths.get(getClass().getClassLoader().getResource("expected-foo.json").toURI());
String expectedJson = Files.lines(resource).collect(Collectors.joining());
server.expect(once(), requestTo("http://example.org/api/foo"))
.andExpect(method(HttpMethod.POST))
.andExpect(MockRestRequestMatchers.content().json(expectedJson))
.andRespond(withSuccess());
// Invoke your service here
fooService.post();
server.verify();
}
As per the documentation, you could match requests using json paths on Mock. For example;
RestTemplate restTemplate = new RestTemplate()
MockRestServiceServer server = MockRestServiceServer.bindTo(restTemplate).build();
server.expect(ExpectedCount.once(), requestTo(path))
.andExpect(method(HttpMethod.POST))
.andExpect(jsonPath("$", hasSize(1)))
.andExpect(jsonPath("$[0].someField").value("some value"))
Note: I haven't tested this.
But I have achieved what you are looking for using Wire Mock many times. That's again a much better option than MockRestServiceServer. Why do I say so?
wide adoption and support
more elegant and extensive request & response matching
highly configurable
record and playback
configurable security/auth
you could even dockerise this
Have a look at http://wiremock.org/docs/request-matching/
I think your approach using a stub server (you could use WireMock for this) is fine if you want to check once, manually.
Alternatively you could add a request logger to your RestTemplate which logs each request. That would make it easier to check if the sent request is correct any time if problems arise.
Related
I have a Java project with various #RequestMapping annotations.I want to make a new project which can use this #RequestMapping,is that possible
Of course you can.
If I understand your question correctly, do you want to use the data provided by a Spring application in another application?
It's not that hard, you just have to keep a few things in mind.
The applications have to run on different ports, of course both applications have to be started.
For example, App1 has a #RequestMapping #GetMapping for personal data.
The path is e.g. http://localhost:8080/persondata
In the second application, you only need to address the API endpoint if you need this data.
This can be done with RestTemplate, for example.
#RestController
#RequestMapping("/persondata")
class PersonDataRestController {
private final Service personService;
public PersonDataRestController(Service personService) {
this.personService = personService;
}
#GetMapping
public ResponseEntity<Collection<?>> getAllPersonData() {
return ResponseEntity.ok(personService.allPersonData());
}
}
You just have to replace the Service with your PersonService or whatelse.
in the second application you can call the REST endpoint with RestTemplate for example
RestTemplate template = new RestTemplate();
try{
ResponseEntity<ArrayList<?>> response = template.exchange("http://localhost:8080/persondata", HttpMethod.GET, null, new ParameterizedTypeReference<ArrayList<?>>() {});
In terms of the specific application, you may need a DTO object.
For this topic i can suggest you this website
I hope I could answer your question
I will answer my question myself, but I am not happy with my solution, so if there is a ready-made convenience class/method doing the same, let me know.
Problem statement
I am using Spring MockRestServiceServer in unit tests to mock a REST service call. I'd like to have quick access to the request body which comes to the mock REST server. Typically for logging or just for evaluating during the debugging.
The context for usage is as follows:
import org.springframework.test.web.client.MockRestServiceServer;
class MyTest {
#Test
void myTest() {
MockRestServiceServer mockServer = ...;
mockServer
.expect(MockRestRequestMatchers.method(HttpMethod.POST))
.andExpect(MockRestRequestMatchers.requestTo("http://mock.example.com/myservice"))
// The following method does not exist, it's what I'd like to have
.andCapture(body -> {
/* do something with the body */
log.info(body);
}) // the place for the Captor
.andRespond(MockRestResponseCreators.withSuccess("The mock response", MediaType.TEXT_PLAIN))
;
}
}
Question
Is there a ready-made class/method which would provide this "andCapture(body -> {})" functionality out of the box?
The best solution I have so far is this:
.andExpect(request -> {
final String body = ((ByteArrayOutputStream) request.getBody()).toString(StandardCharsets.UTF_8);
/* do something with the body */
log.info(body);
})
However, I'd expect that there might exist a convenience method for capturing directly the request body.
I have a simple PUT endpoint in a Spring Boot application:
#PutMapping()
public ResponseEntity<String> upload(#RequestParam("cats") MultipartFile file) throws IOException {
I'm trying to create a test for the controller using MockMVC:
import org.springframework.test.web.servlet.MockMvc;
#RunWith(SpringRunner.class)
#WebMvcTest(CatController.class)
public class CatControllerTest {
#Autowired
private MockMvc mockMvc;
...
For POST endpoints I use something like that:
MockMultipartFile multipartFile = new MockMultipartFile("file", new FileInputStream("myCats.csv"));
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).alwaysDo(print()).build();
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.multipart("/uploadCats").file(multipartFile))
.andExpect(status().isOk())
.andReturn();
But when trying to translate the above call to PUT endpoint I find that I can create
MockMvcRequestBuilders.put("/catUpload")
But then I can't chain to it the multipart
Or I can do:
MockMvcRequestBuilders.multipart(...)
But then I can't chain to it the put.
I saw some post about this problem, but they all were few years old. Is there a way to do it?
Using this great example I was able to solve the issues.
I'm attaching here the code with the minor updates to the post from 2016:
MockMultipartHttpServletRequestBuilder builder =
MockMvcRequestBuilders.multipart("/catUpload");
builder.with(request -> {
request.setMethod("PUT");
return request;
});
MvcResult result = mockMvc.perform(builder
.file(multipartFile))
.andExpect(status().isOk())
.andReturn();
As of 2022 with Spring 5.3.17 it's possible to avoid explicit builder creation leveraging static multipart request mock builder that replaces deprecated fileUpload
Unfortunately it sill has method property hardcoded with POST value, forcing you to change it to PUT by its with method accepting a RequestPostProcessor as parameter.
Being the latter a Functional Interface, lets you reduce the code by lambda expression to something like this in one of its simplest form, involving just a path parameter with value of 99 and a single file to upload and expecting a 200 response code:
mockMvc.perform(multipart("/endpoint/{pathParameter}/upload_handler", 99)
.file(mockFileToUpload)
.with(req -> { req.setMethod("PUT"); return req; }))
.andExpect(status().isOk())
At current time I didn't find any RequestPostProcessor implementation giving the ability to apply needed method modification through a builder pattern, so we're still forced using void mutator setMethod and explicitly returning mutated argument.
Since I want to use the RestTemplate from Spring I want to use the same class as well for Unit-Testing. The idea would be to download a JSON-File and save it locally for the purpose of testing. Therefore I would like to change the URI from a HTTP to a File address. When it as File-address I get an Excpetion
Exception in thread "main" java.lang.IllegalArgumentException: Object of class [sun.net.www.protocol.file.FileURLConnection] must be an instance of class java.net.HttpURLConnection
urlGETList = "http://api.geonames.org/countryInfoJSON?username=volodiaL";
RestTemplate restTemplate = new RestTemplate();
CountryInfoResponse results = restTemplate.getForObject(urlGETList, CountryInfoResponse.class);
Any ideas how I can use the same classes for Unit-Testing?
I think you could look into Wiremock.
Wiremock allows subbing of requests. The advantage is that you really test the complete stack and your tests make real requests against a server responding with mock responses. These mock response bodies can be files (there are other possibilities as well.)
In your unit test you set up wiremock server like this:
#Rule
public WireMockRule wireMockRule = new WireMockRule(port);
Then you can setup a stub with a file response like this:
public void givenResponse(int statusCode, MediaType contentType, String bodyPath) {
String responseBody;
try (InputStream data = new ClassPathResource(bodyPath).getInputStream()) {
responseBody = copyToString(data, UTF_8);
}
stubFor(any(urlPathEqualTo(getWireMockUri().getPath()))
.willReturn(aResponse()
.withStatus(statusCode)
.withHeader("Content-Type", contentType.toString())
.withHeader("Content-Length", String.valueOf(responseBody.length()))
.withBody(responseBody)
));
}
You could also put the complete stub into a stub file like described here
Afterwards you can check if a certain request has been made:
verify(postRequestedFor(urlEqualTo("/form"))
.withHeader("Content-Type", containing(MULTIPART_FORM_DATA_VALUE)));
You can find out more about verification here
You would use the RestTemplate to make requests. You just need to have host and port configurable so you can use localhost and the wiremock port in your tests.
I'm trying to write a JUnit test case which tests a method in a helper class. The method calls an external application using REST and it's this call that I am trying to mock in the JUnit test.
The helper method makes the REST call using Spring's RestTemplate.
In my test, I create a mock REST server and mock REST template and instanitiate them like this:
#Before
public void setUp() throws Exception {
mockServer = MockRestServiceServer.createServer(helperClass.getRestTemplate());
}
I then seed the mock server so that it should return an appropriate response when the helper method makes the REST call:
// response is some XML in a String
mockServer
.expect(MockRestRequestMatchers.requestTo(new URI(myURL)))
.andExpect(MockRestRequestMatchers.method(HttpMethod.GET))
.andRespond(MockRestResponseCreators.withStatus(HttpStatus.OK)
.contentType(MediaType.APPLICATION_XML)
.body(response));
When I run my test, the helper method receives a null response from the REST call it makes and the test fails.
The REST URL that the helper makes has query params and looks like this: "http://server:port/application/resource?queryparam1=value1&queryparam2=value2".
I've tried putting the URL ("http://server:port/application/resource") both with and without the query parameters in the "myURL" variable (to elicit a match so that it returns a response), but can not get the mock server to return anything.
I've tried searching for examples of this kind of code but have yet to find anything which seems to resemble my scenario.
Spring version 4.1.7.
Thanks in advance for any assistance.
When you create an instance of MockRestServiceServer you should use existing instance of RestTemplate that is being used by your production code. So try to inject RestTemplate into your test and use it when invoking MockRestServiceServer.createServer - don't create new RestTemplate in your tests.
Seems that you are trying to test the rest-client, the rest-server should be tested in other place.
You are using RestTemplate -> To call the service. Then, tried to mock RestTemplate and its call's results.
#Mock
RestTemplate restTemplateMock;
and Service Under Test Class
#InjectMocks
Service service;
Let say, Service has a method to be test as
public void filterData() {
MyResponseModel response = restTemplate.getForObject(serviceURL, MyResponseModel.class);
// further processing with response
}
Then, to test filterData method, you need to mock the response from restTemplate call such as
mockResponseModel = createMockResponse();
Mockito.when(restTemplateMock.getForObject(serviceURL, MyResponseModel.class)).thenReturn(mockResponseModel);
service.filterData();
//Other assert/verify,... go here
You can create a new instance of RestTemplate however you have to pass it in your createServer method like this:
private RestTemplate restTemplate = new RestTemplate();
#BeforeEach
public void setUp() {
MockitoAnnotations.initMocks(this);
mockServer = MockRestServiceServer.createServer(restTemplate);
client.setRestTemplate(restTemplate);
}