Mocking HttpClient.execute issues: Mockito - http

I am trying to test this method.
#Override
public JSON connectResource() throws IOException {
//get the location and credentials for the certificates
System.setProperty("javax.net.ssl.trustStore", "C:/Program Files/Java/jdk1.7.0_40/jre/lib/security/cacerts");
System.setProperty("javax.net.ssl.trustStorePassword", "changeit");
HttpRequest httpRequest = new HttpGet(url);
System.out.println("hello");
httpRequest.addHeader("Accept", "application/json");
HttpResponse response = httpClient.execute((HttpUriRequest) httpRequest);
System.out.println("hello1");
HttpEntity httpEntity = response.getEntity();
String data = this.getData(httpEntity);
return JSONSerializer.toJSON(data.toString());
}
My set up method is:
#Before
public void setUp() throws Exception{
mockHttpClient = mock(DefaultHttpClient.class);
mockHttpRequest = mock(HttpUriRequest.class);
mockHttpResponse = mock(BasicHttpResponse.class);
mockHttpEntity = mock(HttpEntity.class);
mockInputStream = mock(InputStream.class);
mockInputStreamReader = mock(InputStreamReader.class);
mockBufferedReader = mock(BufferedReader.class);
mockHttpGet = mock(HttpGet.class);
mockHttpRequestBase = mock(HttpRequestBase.class);
//when(mockHttpClient.execute(Mockito.isA(HttpUriRequest.class))).thenReturn(mockHttpResponse);
//when(mockHttpClient.execute(mockHttpRequest)).thenReturn(mockHttpResponse);
//when(mockHttpClient.execute(mockHttpRequestBase)).thenReturn(mockHttpResponse);
//when(mockHttpClient.execute(mockHttpGet)).thenReturn(mockHttpResponse);
when(mockHttpResponse.getEntity()).thenReturn(mockHttpEntity);
when(mockHttpEntity.getContent()).thenReturn(mockInputStream);
PowerMockito.whenNew(InputStreamReader.class)
.withArguments(mockInputStream).thenReturn(mockInputStreamReader);
PowerMockito.whenNew(BufferedReader.class)
.withArguments(mockInputStreamReader).thenReturn(mockBufferedReader);
PowerMockito.when(mockBufferedReader.readLine())
.thenReturn(JSON_STRING)
.thenReturn(null);
PowerMockito.whenNew(HttpGet.class).withArguments(VALID_URL)
.thenReturn(mockHttpGet);
}
And my test case is :
#Test
public void testConnectResource() throws IOException {
when(mockHttpClient.execute(mockHttpGet)).thenReturn(mockHttpResponse);
HttpConnectGithub connHandle = new HttpConnectGithub(VALID_URL);
JSON jsonObject = connHandle.connectResource();
System.out.println(jsonObject);
//assertThat(jsonObject, instanceOf(JSON.class));
}
However, the execution stops at
HttpResponse response = httpClient.execute((HttpUriRequest) httpRequest);
you can see all that I tried in the comments of my set up method.
Does anyone find an issue with anything? I debugged through my test case and all mock objects are properly initialized.
I have tried exchanging HttpUriRequest and HttpRequest, HttpResponse and BasicHttpResponse etc but without much luck.
Please guide on how to tackle this issue.

Part of the problem you're running into is matching the arguments:
#Test
public void testConnectResource() throws IOException {
when(mockHttpClient.execute(mockHttpGet)).thenReturn(mockHttpResponse);
HttpConnectGithub connHandle = new HttpConnectGithub(VALID_URL);
JSON jsonObject = connHandle.connectResource();
System.out.println(jsonObject);
//assertThat(jsonObject, instanceOf(JSON.class));
}
With the line you've specified above
when(mockHttpClient.execute(mockHttpGet)).thenReturn(mockHttpResponse);
The mocking will only trigger when the instance of mockHttpGet you've defined is passed.
Your method under test on the other hand is creating a new HttpGet instance which is not going to be the same as the mockHttpGet instance. You will need to alter the 'when' statement so that you have something like
when(mockHttpClient.execute(Matchers.any(HttpGet.class))).thenReturn(mockHttpResponse);
I'm doing this exclusively from memory so the Matchers.any() may be incorrect, but you should be able to make headway based on what I've given you above.

The problem is with mockHttpClient. It is not able to mock it automatically for some reason. The fix is to pass httpclient as a parameter through some method (constructor in my case)

Related

How to do GZIP compression while writing directly to response

I am trying to compress my response when the request header contains gzip in Accept-Encoding. However, adding following to app.properties only works when controller method is returning an object.
server.compression.enabled=true
server.compression.min-response-size=1
server.compression.mime-types=application/json,application/xml,text/html,text/xml,text/plain,application/javascript,text/css
I am writing directly to response stream. So, above compression properties don't work.
My method looks like this:
#GetMapping(path = "/something", produces = MediaType.TEXT_PLAIN_VALUE)
public void getSomething(#RequestParam(name = "paramA") String paramA,
HttpServletRequest request,
HttpServletResponse response) throws IOException {
PrintWriter writer = null;
if (request.getHeader("Accept-Encoding").contains("gzip")) {
writer = new PrintWriter(new OutputStreamWriter(new GZIPOutputStream(response.getOutputStream())));
response.addHeader(HttpHeaders.CONTENT_ENCODING, "gzip");
} else {
writer = response.getWriter();
}
final PrintWriter fwriter = writer;
someObjectInstance.getSomething(paramA).forEach(x -> {
fwriter.write(x.toString());
});
fwriter.flush();
}
When I curl the above method, I get an empty file.
I did try to use the GzipFilter by referring to the following link.
http://www.javablog.fr/javaweb-gzip-compression-protocol-http-filter-gzipresponsewrapper-gzipresponsewrapper.html, which works by the way.
However, the filter requires alot of boilerplate code. Is there a way I can make changes in controller method and solve the problem as in the link mentioned above.
Just realised I was not closing the writer.
After adding fwriter.close(); in the end, the problem was solved.

response body from restTemplate is getting truncated when downloading a file

I am using spring RestTemplate to download a file. The file size is small.
I want to get base64 encoded String. but I see the base64 encoded string is truncated from what it is supposed to be.
Here is my code
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(
new ByteArrayHttpMessageConverter());
StreamResourceReader reader = new StreamResourceReader();
restTemplate.execute(uri, HttpMethod.POST, null,
new StreamResponseExtractor(reader));
return reader.getEncodedString();
StreamResourceReader.java
public class StreamResourceReader {
private String encodeString;
public void read(InputStream content) {
try {
encodeString = Base64.encodeBase64String(IOUtils.toByteArray(content));
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
public ByteArrayOutputStream getOutputStream(){
return outputStream;
}
public String getEncodedString() {
return encodeString;
}
}
StreamResponseExtractor.java
public class StreamResponseExtractor implements ResponseExtractor<InputStream> {
private StreamResourceReader reader;
public StreamResponseExtractor(StreamResourceReader resourceReader) {
this.reader=resourceReader;
}
#Override
public InputStream extractData(ClientHttpResponse response) throws IOException {
reader.read(response.getBody());
return null;
}
}
EDIT
just found out that inputStream is truncated. I dont know why and what the fix is. any help here would be appreciated.
Thanks
To confirm if your input stream is indeed truncated you can try few things. What IOUtils.toByteArray(content) does is buffers internally the content of input stream and returns the buffer. You can compare the length of buffer array with the byte array the file actually represents. You can do latter with below code
String filePath = "/test.txt";
byte[] fileByteArray= Files.readAllBytes(Paths.get(filePath));
Also ClientHttpResponse ( client view of http response) too has the inputstream available which you can check for content.
InputStream getBody() throws IOException;
As a test for this scenario , I created spring boot Rest client using Rest Template (using the code you shared) and a service for file download again using Spring Boot. On comparing the base encoded String from download vs direct file access, both return same content (compared using String equals method).
UPDATE: Another thing worth trying is just use java.net.HttpURLConnection
in a simple program (for help see here) and try to download the content and check whether this works properly because behind all the Spring abstractions, in this case the underlying object used is HttpURLConnection only
SimpleClientHttpResponse extends AbstractClientHttpResponse {
public InputStream getBody() throws IOException {
InputStream errorStream = this.connection.getErrorStream();
this.responseStream = (errorStream != null ? errorStream : this.connection.getInputStream());
return this.responseStream;
}
...........
...........
}
If this also gives you the same issue, then it's time to look at the server side. May be the server is not sending the complete data.

Modify request/observable between retry

I have rxjava(observable) + retrofit2 together to make http requests to my application. I create OkHttpClient once for app and don't want to recreate it.
I have retry logic implemented on observable level - using filter, retryWhen together.
What I want - if request finished with error from server side, i want to retry it and send additional header with it.
So, I dont understand neither how can I modify observable inside retryWhen nor how to get the knowledge about observable from interceptor level.
Any ideas and/or knowledge about possible approaches?
You need to create your own Interceptor implementation where you can setup the header logic. Something like
public class FallbackInterceptor implements Interceptor {
static String header1Key = "key1";
static String extraHeaderKey = "key2";
String header1, extraHeader;
boolean useextraheader = false;
public FallbackInterceptor(string header1, string extraheader) {
this.header1 = header1;
this.extraheader = extraheader;
}
public void setUseExtraHeader(boolean useextraheader) {
this.userextraheader = useextraheader;
}
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request original = chain.request();
// Add request headers
Request.Builder requestBuilder = original.newBuilder().header(header1Key, header1);
if (useExtraHeader) {
requestBuilder = requestBuilder.header(extraHeaderKey, extraHeader)
}
Request newRequest = requestBuilder.method(original.method(), original.body())
.build();
// Return the response
return chain.proceed(request);
}
}
Add this to an okhttpclient and have your retrofit instance use this this. You can then manipulate the extraheader flag in your retry logic.

HttpMediaTypeNotAcceptableException: Could not find acceptable representation spring mvc

This is driving me crazy! I'm trying to serve a JPG image. I am sure this method was working fine the other day, so I don't know what's changed. I've tried many different things to get it to work but I can't seem to get past the exception.
Basically I'm trying to serve an image from the database.
I thought maybe the actual bytes are corrupt so I wrote them to a file, and checked the file content. Just in Finder on Mac, the file in the temp directory looks fine in the preview application so I'm pretty sure it's not the content itself causing the problem.
This is the controller method:
#RequestMapping(value="/binaries/**", method = RequestMethod.GET, produces={MediaType.APPLICATION_JSON_VALUE, MediaType.IMAGE_GIF_VALUE,
MediaType.IMAGE_JPEG_VALUE, MediaType.IMAGE_PNG_VALUE, "application/javascript"})
public #ResponseBody
ResponseEntity<byte[]> serveResource(WebRequest webRequest, HttpServletResponse response, String uri) throws IOException {
String path = (String)request.getAttribute( HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE );
BinaryFile bf = binaryService.findByUri(path);
String tmpdir = System.getProperty("java.io.tmpdir");
File dest = new File(tmpdir + File.separator + bf.getFileName());
FileUtils.writeByteArrayToFile(dest, bf.getResource());
logger.debug("file written: " + dest.getAbsolutePath());
// response.addHeader("Cache-Control", "public, max-age=3600");
if (webRequest.checkNotModified(bf.getLastModifiedDate().toDate().getTime()))
{
return null;
};
return ResponseEntity.ok().contentType(MediaType.IMAGE_JPEG).body(bf.getResource());
}
This is the exception:
Request: http://localhost:8080/binaries/products/shortcode_1/test_image2.jpg raised org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
Anyone have any ideas? It's Spring 4.1.4.RELEASE
Oh never mind, I figured out what changed. I'd overridden the MessageConverters because I was working on some Jackson stuff, so the fix was that I needed to manually add back the ByteArrayHttpMessageConverter.
#Bean
public ByteArrayHttpMessageConverter byteArrayHttpMessageConverter(){
ByteArrayHttpMessageConverter bam = new ByteArrayHttpMessageConverter();
List<org.springframework.http.MediaType> mediaTypes = new LinkedList<org.springframework.http.MediaType>();
mediaTypes.add(org.springframework.http.MediaType.APPLICATION_JSON);
mediaTypes.add(org.springframework.http.MediaType.IMAGE_JPEG);
mediaTypes.add(org.springframework.http.MediaType.IMAGE_PNG);
mediaTypes.add(org.springframework.http.MediaType.IMAGE_GIF);
mediaTypes.add(org.springframework.http.MediaType.TEXT_PLAIN);
bam.setSupportedMediaTypes(mediaTypes);
return bam;
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter mapper = new MappingJackson2HttpMessageConverter();
ObjectMapper om = new ObjectMapper();
om.registerModule(new JodaModule());
om.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.setObjectMapper(om);
converters.add(mapper);
converters.add(byteArrayHttpMessageConverter());
super.configureMessageConverters(converters);
}

How can i unit test an EntitySetController

i try to unit test an EntitySetController. I can test Get but have problems in testing the Post Method.
I played around with SetODataPath and SetODataRouteName but when i call this.sut.Post(entity) i get a lot of errors regarding missing Location Header, missing OData-Path, missing Routes.
I am at my wit's end.
Is there anybody out there who has successfully testet their EntitySetController?
Has anybody an idea for me?
Maybe should i test only the protected overrided methods from my EntitySetController implementation? But how can i test protected methods?
Thanks for your help
Came here looking for a solution aswell. This seems to work however not sure if there is a better way.
The controller needs a minimum of CreateEntity and GetKey overrides:
public class MyController : EntitySetController<MyEntity, int>
{
protected override MyEntity CreateEntity(MyEntity entity)
{
return entity;
}
protected override int GetKey(MyEntity entity)
{
return entity.Id;
}
}
Where MyEntity is really simple:
public class MyEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
Looks like you need at least:
+ Request with a URI
+ 3 keys in the request header, MS_HttpConfiguration, MS_ODataPath and MS_ODataRouteName
+ A HTTP configuration with a route
[TestMethod]
public void CanPostToODataController()
{
var controller = new MyController();
var config = new HttpConfiguration();
var request = new HttpRequestMessage();
config.Routes.Add("mynameisbob", new MockRoute());
request.RequestUri = new Uri("http://www.thisisannoying.com/MyEntity");
request.Properties.Add("MS_HttpConfiguration", config);
request.Properties.Add("MS_ODataPath", new ODataPath(new EntitySetPathSegment("MyEntity")));
request.Properties.Add("MS_ODataRouteName", "mynameisbob");
controller.Request = request;
var response = controller.Post(new MyEntity());
Assert.IsNotNull(response);
Assert.IsTrue(response.IsSuccessStatusCode);
Assert.AreEqual(HttpStatusCode.Created, response.StatusCode);
}
I'm not too sure about the IHttpRoute, in the aspnet source code (I had to link to this to figure this all out) the tests use mocks of this interface. So for this test I just create a mock of this and implement the RouteTemplate property and GetVirtualPath method. All the others on the interface were not used during the test.
public class MockRoute : IHttpRoute
{
public string RouteTemplate
{
get { return ""; }
}
public IHttpVirtualPathData GetVirtualPath(HttpRequestMessage request, IDictionary<string, object> values)
{
return new HttpVirtualPathData(this, "www.thisisannoying.com");
}
// implement the other methods but they are not needed for the test above
}
This is working for me however I am really not too sure about the ODataPath and IHttpRoute and how to set it correctly.
In addition to the answer from #mynameisbob, I have found you also may need to set the HttpRequestContext as well on the Request properties:
var requestContext = new HttpRequestContext();
requestContext.Configuration = config;
request.Properties.Add(HttpPropertyKeys.RequestContextKey, requestContext);
I needed the above additions for example when creating an HttpResponseMessage as follows:
public virtual HttpResponseException NotFound(HttpRequestMessage request)
{
return new HttpResponseException(
request.CreateResponse(
HttpStatusCode.NotFound,
new ODataError
{
Message = "The entity was not found.",
MessageLanguage = "en-US",
ErrorCode = "Entity Not Found."
}
)
);
}
Without having the HttpRequestContext set, the above method will throw an Argument Null Exception as the CreateResponse extension method attempts to get the HttpConfiguration from the HttpRequestContext (rather than directly from the HttpRequest).
OK updated answer.
I've also found to support executing a returned IHttpActionResult successfully, a few more things are needed.
Here is the best approach I found so far, I'm sure there is a better way but this works for me:
// Register OData configuration with HTTP Configuration object
// Create an ODataConfig or similar class in App_Start
ODataConfig.Register(config);
// Get OData Parameters - suggest exposing a public GetEdmModel in ODataConfig
IEdmModel model = ODataConfig.GetEdmModel();
IEdmEntitySet edmEntitySet = model.EntityContainers().Single().FindEntitySet("Orders");
ODataPath path = new ODataPath(new EntitySetPathSegment(edmEntitySet));
// OData Routing Convention Configuration
var routingConventions = ODataRoutingConventions.CreateDefault();
// Attach HTTP configuration to HttpRequestContext
requestContext.Configuration = config;
// Attach Request URI
request.RequestUri = requestUri;
// Attach Request Properties
request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, config);
request.Properties.Add(HttpPropertyKeys.RequestContextKey, requestContext);
request.Properties.Add("MS_ODataPath", path);
request.Properties.Add("MS_ODataRouteName", "ODataRoute");
request.Properties.Add("MS_EdmModel", model);
request.Properties.Add("MS_ODataRoutingConventions", routingConventions);
request.Properties.Add("MS_ODataPathHandler", new DefaultODataPathHandler());
Also, to get the correct Location header values etc, you really want to call your Web Api application OData configuration code.
So rather than using:
config.Routes.Add("mynameisbob", new MockRoute());
You should separate the portion of the WebApiConfig class that sets up your OData routes into a separate class (e.g. ODataConfig) and use that to register the correct routes for your tests:
e.g.
ODataConfig.Register(config);
The only things you then have to watch out for is that the following lines match your routing configuration:
request.Properties.Add("MS_ODataPath", new ODataPath(new EntitySetPathSegment("MyEntity")));
request.Properties.Add("MS_ODataRouteName", "mynameisbob");
So if your Web API OData configuration is as follows:
config.Routes.MapODataRoute("ODataRoute", "odata", GetEdmModel());
private static IEdmModel GetEdmModel()
{
ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<MyEntity>("MyEntities");
IEdmModel model = modelBuilder.GetEdmModel();
return model;
}
Then this is the correct configuration:
request.Properties.Add("MS_ODataPath", new ODataPath(new EntitySetPathSegment("MyEntities")));
request.Properties.Add("MS_ODataRouteName", "ODataRoute");
With this in place, your Location header will be generated correctly.
In addition to everything here, I had to manually attach the context to the request, as well as create route data. Unfortunately there is no way I found to unit-test without a dependency on route/model configuration.
So using a route called "ODataRoute" which is all part of the normal configuration established in my static ODataConfig.Configure() method (same as above, it creates the model and calls a bunch of MapODataServiceRoute), the following code works to prepare a controller for a test:
protected static void SetupControllerForTests(ODataController controller,
string entitySetName, HttpMethod httpMethod)
{
//perform "normal" server configuration
var config = new HttpConfiguration();
ODataConfig.Configure(config);
//set up the request
var request = new HttpRequestMessage(httpMethod,
new Uri(string.Format("http://localhost/odata/{0}", entitySetName)));
//attach it to the controller
//note that this will also automagically attach a context to the request!
controller.Request = request;
//get the "ODataRoute" route from the configuration
var route = (ODataRoute)config.Routes["ODataRoute"];
//extract the model from the route and create a path
var model = route.PathRouteConstraint.EdmModel;
var edmEntitySet = model.FindDeclaredEntitySet(entitySetName);
var path = new ODataPath(new EntitySetPathSegment(edmEntitySet));
//get a couple more important bits to set in the request
var routingConventions = route.PathRouteConstraint.RoutingConventions;
var pathHandler = route.Handler;
//set the properties of the request
request.SetConfiguration(config);
request.Properties.Add("MS_ODataPath", path);
request.Properties.Add("MS_ODataRouteName", "ODataRoute");
request.Properties.Add("MS_EdmModel", model);
request.Properties.Add("MS_ODataRoutingConventions", routingConventions);
request.Properties.Add("MS_ODataPathHandler", pathHandler);
//set the configuration in the request context
var requestContext = (HttpRequestContext)request.Properties[HttpPropertyKeys.RequestContextKey];
requestContext.Configuration = config;
//get default route data based on the generated URL and add it to the request
var routeData = route.GetRouteData("/", request);
request.SetRouteData(routeData);
}
This took me the better part of a few days to piece together, so I hope this at least saves someone else the same.

Resources