How can I mock GET http request using JAVA?
I have this method:
public HTTPResult get(String url) throws Exception{
try {
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
return new HTTPResult(response.getBody(), response.getStatusCode().value());
}
catch (ResourceAccessException e) {
String responseBody = e.getCause().getMessage();
JSONObject obj = new JSONObject(responseBody);
return new HTTPResult(obj.getString("responseBody"), Integer.parseInt(obj.getString("statusCode")));
}
}
How can i verify i am getting: responseBody - some json and statusCode for example 200?
If you want a "true" unit test, you have to look into using a mocking framework, such as EasyMock, or Mockito (I recommend the later one). That might make it necessary to rework your production code, as calls to new() often make problems with these frameworks (there are other frameworks that are better there: JMockit, or PowerMockito, but again: if possible go with Mockito).
If you are rather asking for a more "end to end" kind of test: there are test frameworks for Jersey ( see here for example ). Meaning: you can actually "unit test" your REST endpoints almost completely, without the need of running a real server.
Related
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'm trying to create a ClientResponse in test and use it for testing a service, which also does deserialization with standard way response.bodyToMono(..class..). But it appears that there is something wrong in the way I build a fake client response. Because I receive UnsupportedMediaTypeException in tests.
Nevertheless the same code work fine in runtime SpringBoot app, when WebClient returns ClientResponse (which is built internally).
Let's see at the simplest case hich fails with
org.springframework.web.reactive.function.UnsupportedMediaTypeException:
Content type 'application/json' not supported for bodyType=java.lang.String[]
void test()
{
String body = "[\"a\", \"b\"]";
ClientResponse response = ClientResponse.create(HttpStatus.OK)
.header(HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_JSON_VALUE)
.body(body)
.build();
String[] array = response.bodyToMono(String[].class).block();
assertEquals(2, array.length);
}
Please, help me to undeerstand, how the client response should be build to allow a standard (json -> object) deserialization in test environment.
A ClientResponse created manually does not have access to Jackson2Json exchange strategies in default list. Probably it could be configured with Spring auto-configuration, which is turned off in tests without Spring context.
Here is the straightforward way to force (de)serialization String <-> json:
static ExchangeStrategies jacksonStrategies()
{
return ExchangeStrategies
.builder()
.codecs(clientDefaultCodecsConfigurer ->
{
clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(new ObjectMapper(), MediaType.APPLICATION_JSON));
clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(new ObjectMapper(), MediaType.APPLICATION_JSON));
}).build();
}
Then use it in the create function
ClientResponse.create(HttpStatus.OK, jacksonStrategies())...
Suppose the application is dependent on a REST service on a external server, http://otherserver.com. For testing, I would like to simulate the external rest call (via Wiremock) within a JUnit environment. Starting a seperate server consumes time and is not easy. Working with WiremockRule looks the right direction. Creating simulation controllers is not an elegant way as Wiremock is available.
E.g. get( "http://otherserver.com/service3/");
PS: of course I know that I can simulate a REST call via Mockito.
Simulating localhost with Wiremock is easy. How can I use that code to simulate other servers and services? I copied parts from the popular Baeldung examples.
public class WireMockDemo {
#Rule
public WireMockRule wireMockRule = new WireMockRule();
#Test
public void wireMockTestJunitOtherServer() {
try {
// **this does not work...**
configureFor("otherserver.com", 8080);
stubFor(get(urlPathMatching("/service2/.*"))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody("\"testing-library\": \"WireMock\"")));
// Test via simple client
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet request = new HttpGet("http://otherserver:8080/service2/test");
HttpResponse httpResponse = httpClient.execute(request);
String stringResponse = convertHttpResponseToString(httpResponse);
System.out.println( "Response = " + stringResponse);
// Test via JUnit
verify(getRequestedFor(urlEqualTo("/service2/wiremock")));
assertEquals(200, httpResponse.getStatusLine().getStatusCode());
assertEquals("application/json", httpResponse.getFirstHeader("Content-Type").getValue());
assertEquals("\"testing-library\": \"WireMock\"", stringResponse);
} catch( Exception e) {
e.printStackTrace();
}
}
// Support methods
private String convertHttpResponseToString(HttpResponse httpResponse) throws IOException {
InputStream inputStream = httpResponse.getEntity().getContent();
return convertInputStreamToString(inputStream);
}
private String convertInputStreamToString(InputStream inputStream) {
Scanner scanner = new Scanner(inputStream, "UTF-8");
String string = scanner.useDelimiter("\\Z").next();
scanner.close();
return string;
}
}
Your application code should not have the http://otherserver.com hardcoded, it should be configurable. When running normally it should point to http://otherserver.com, when running in test mode it should be pointed to http://localhost:<port> where <port> is where you have started your Wiremock server (preferably dynamic to avoid port clashes)
TL; DR:
No, you cannot.
What WireMock does, is to establish a Jetty server simulating a remote server you need to send request to. However, it does not change your hosts file or DNS mapping and automatically "redirect" your real request for remote server to localhost. In tests you still need to send request to localhost.
What you can do, if you are using Spring Boot, is to create two application.yml file(or another properties file) in main and test package, with same structure of keys, but the value in src/main/resources/application.yml is the real URL you request(like http://example.com/api), and that in src/test/resources/application.yml you put localhost/api.
By the way, to clarify, MockMvc is not for simulation of external 3rd party server request that your application depends on, but requests sent to the endpoints of your application. In MockMvc tests, your application is who receives the request, but in WireMock tests, your applications sends request.
Some working example:
// configure static, class-level rule for all tests, like #BeforeClass.
// this launches a Jetty server with configurations
#ClassRule
public static WireMockClassRule classRule = new WireMockClassRule(options().
port(80).httpsPort(443));
// Divide #ClassRule and #Rule,
// to bypass JUnit limitation of "#Rule cannot be static"
#Rule
public WireMockClassRule rule = classRule;
#Test
public void shouldReturnGivenJson() throws Exception {
// stubFor() also works; givenThat() is more TDD-ish
givenThat(post(urlEqualTo("/service2/test")) // <----- note here: without host name
.willReturn(WireMock.aResponse()
.withStatus(HttpStatus.OK.value())
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
.withBody("{\"status\":\"up\"}")));
// .... your connection here
I suggest to begin with urlEqualTo(), without messing around with regex. Then you progress to urlMatching().
Also, use org.apache.http.util.EntityUtils to get content from the response. This is the official, built-in way to process the response. And, use a ResponseHandler because it will consume() the response without manually cleaning the resources.
Check HttpClient documentation for more details.
Recently I've started to use Spring's MockRestServiceServer to verify my RestTemplate based requests in tests.
When its used for simple get/post request - all good, however, I couldn't figure out how to use it with POST multipart request:
For example, my working code that I would like to test looks like this:
public ResponseEntity<String> doSomething(String someParam, MultipartFile
file, HttpHeaders headers) { //I add headers from request
MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.add("file", new ByteArrayResource(file.getBytes()) {
#Override
public String getFilename() {
return file.getOriginalFilename();
}
});
map.add("someParam", someParam);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new
HttpEntity<>(map, headers);
return this.restTemplate.exchange(
getDestinationURI(),
HttpMethod.POST,
requestEntity,
String.class);
}
So my question is How I can specify my expectations with org.springframework.test.web.client.MockRestServiceServer? Please notice, that I don't want to just mock the "exchange" method with mockito or something, but prefer to use MockRestServiceServer
I'm using spring-test-4.3.8.RELEASE version
A code snippet would be really appreciated :)
Thanks a lot in advance
Update:
As per James's request I'm adding non-working test snippet (Spock test):
MockRestServiceServer server = MockRestServiceServer.bindTo(restTemplate).build()
server.expect(once(), requestTo(getURI()))
.andExpect(method(HttpMethod.POST))
.andExpect(header(HttpHeaders.CONTENT_TYPE, startsWith("multipart/form-data;boundary=")))
.andExpect(content().formData(["someParam" : "SampleSomeParamValue", "file" : ???????] as MultiValueMap))
.andRespond(withSuccess("sample response", MediaType.APPLICATION_JSON))
multipartFile.getBytes() >> "samplefile".getBytes()
multipartFile.getOriginalFilename() >> "sample.txt"
I get exception while asserting the request content. The form data is different, because an actual form data is created internally with Content-Disposition, Content-Type, Content-Length per parameter and I don't know how to specify these expected values
Multipart request expectations have been added to MockRestServiceServer in Spring 5.3 - see:
pull request
final version
You can use
content().multipartData(MultiValueMap<String, ?> expectedMap)
Parse the body as multipart data and assert it contains exactly the values from the given MultiValueMap. Values may be of type:
String - form field
Resource - content from a file
byte[] - other raw content
content().multipartDataContains(Map<String,?> expectedMap)
Variant of multipartData(MultiValueMap) that does the same but only for a subset of the actual values.
I think this depends on how deeply you want to test the form data. One way, which is not 100% complete, but is a "good enough" for unit testing (usually) is to do something like:
server.expect(once(), requestTo(getURI()))
.andExpect(method(HttpMethod.POST))
.andExpect(content().string(StringContains.containsString('paramname=Value') ))....
This is ugly and incomplete, but is sometimes useful. Of course, you can also work to make the form setup it's own method and then use mocks to try to verify that the expected parameters are all in place.
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.