0

This is my Actual class for which i am writing junit. I have HtpClient as private and final.

 public class KMSHttpClientImpl implements KMSHttpClient
 {
/**
 * ObjectMapper Instance.
 */
private final ObjectMapper objectMapper = new ObjectMapper ();

/**
 * KMS ConnectionManager Instance.
 */
private final KMSHttpConnectionManager kmsHttpConnectionManager =
        new KMSHttpConnectionManagerImpl ();

/**
 * HttpClient object.
 */

private final HttpClient httpClient;

/**
 * KMSHttpClient constructor.
 */
public KMSHttpClientImpl ()
{
    // TODO PoolingHttpClientConnectionManager object should be closed after use.
    // TODO This needs to be either singleton or should be kept in static block
    final PoolingHttpClientConnectionManager connectionManager =
            kmsHttpConnectionManager.getConnectionManager();
    httpClient = HttpClients.custom()
            .setConnectionManager(connectionManager)
            .build();
}

@Override
public <T> T invokeGETRequest (final String url, final Class<T> clazz)
        throws KMSClientException
{
    final HttpGet httpGet = new HttpGet(url);
    try {
        final HttpResponse response = httpClient.execute(httpGet);
        return objectMapper.readValue(
                response.getEntity().getContent(), clazz);
    } catch (IOException e) {
        throw new KMSClientException("Unable to get the result", e);
    }
}

@Override
public <T> T invokePOSTRequest (final String url, final Object object, final Class<T> clazz)
        throws KMSClientException
{
    final HttpPost httpPost = new HttpPost(url);
    try {
        final HttpResponse response = httpClient.execute(httpPost);
        return objectMapper.readValue(
                response.getEntity().getContent(), clazz);
    } catch (IOException e) {
        throw new KMSClientException("Unable to create the request", e);
    }
}
 }

This is my testclass. I am trying to Mock HttpClient but as it is final i cant mock it. And if i remove final from HttpClient in my KMSHttpClientImpl.java class. I am getting PMd issue saying Private field 'httpClient' could be made final; it is only initialized in the declaration or constructor. What can i do to fix this issue?

public class KMSHttpClientImplTest
{

/**
 * Injecting mocks KMSHttpClientImpl.
 */
@InjectMocks
private KMSHttpClientImpl kmsHttpClientImpl;

/**
 * Mock HttpClient.
 */
@Mock
private HttpClient httpClient;


/**
 * Initial SetUp Method.
 */
@Before
public void setUp ()
{
    initMocks(this);
}

/**
 * Method to test postRequest Method.
 * @throws KMSClientException
 */
@Test
public void testPostRequest () throws KMSClientException
{
    final OrganizationRequest request = getOrganizationRequest();
    final HttpResponse response = prepareResponse(HttpStatus.SC_OK);
    try {
        Mockito.when(httpClient.execute(Mockito.any())).thenReturn(response);
        final OrganizationResponse organizationResponse = kmsHttpClientImpl.invokePOSTRequest(
                ORG_TEST_URL, request, OrganizationResponse.class);
        assertEquals("Id should match", ORG_ID, organizationResponse.getId());
    } catch (IOException e) {
        throw new KMSClientException("Unable to create the request", e);
    }
      }

/**
 * Method to test getRequest Method.
 * @throws KMSClientException
 */
@Test
public void testGetRequest () throws KMSClientException
{
    try {
        final HttpResponse response = prepareResponse(HttpStatus.SC_OK);
        Mockito.when(httpClient.execute(Mockito.any())).thenReturn(response);
        final OrganizationResponse organizationResponse = kmsHttpClientImpl.invokeGETRequest
                (ORG_TEST_URL, OrganizationResponse.class);
        assertEquals("Id should match", ORG_ID, organizationResponse.getId());
    }  catch (IOException e) {
        throw new KMSClientException("Unable to create the request", e);
    }
}

/**
 * Method to organizationRequest Object.
 * @return OrganizationRequest object
 */
public OrganizationRequest getOrganizationRequest ()
{
    return OrganizationRequest.builder().id("test").build();
}

/**
 * Method to getOrganizationResponse String.
 * @return String Object
 */
public String getOrganizationResponse ()
{
    final Map obj=new HashMap();
    obj.put("id", ORG_ID);
    obj.put("uuid", ORG_UUID);
    obj.put("orgKeyId", ORG_KEYID);
    return JSONValue.toJSONString(obj);
}

/**
 * Method to prepare Response.
 * @param expectedResponseStatus
 * @return HttpResponse
 */
private HttpResponse prepareResponse (final int expectedResponseStatus)
{
    final HttpResponse response = new BasicHttpResponse(new BasicStatusLine(
            new ProtocolVersion("HTTP", 1, 1),
            expectedResponseStatus, ""));
    response.setStatusCode(expectedResponseStatus);
    final HttpEntity httpEntity = new StringEntity(getOrganizationResponse(),
            ContentType.APPLICATION_JSON);
    response.setEntity(httpEntity);
    return response;
     }
    }
priyanka
  • 422
  • 2
  • 7
  • 19
  • I came across this question when I was trying to figure out how to mock the response of the send / sendAsync method on HttpClient. For an answer to that, see https://stackoverflow.com/a/76778792/925913. – Andrew Cheong Jul 27 '23 at 10:26

4 Answers4

2

One of the ways to test a HTTP client code would be to not mock your HTTPClient object, but to create mock responses for the http calls and then let your HPPTClient make calls to those URLs. Take a look at Wiremock. http://wiremock.org/docs/ It helps you create a simple mock server and you can stub responses for your URLs. Then invoke your URLs using your client for the test.

  • I don't want to use PowerMock. Is there a way to suppress this PMD warning Private field 'httpClient' could be made final; it is only initialized in the declaration or constructor – priyanka Feb 09 '21 at 08:42
  • I am suggesting to use wiremock which is a different library from powermock. But I get your point. You want to remove final. But then you are being shown it as a issue because it is an issue. I guess you are using PMD for analyzing your code. Why complain when its doing what it is supposed to.! Anyways, if thats what you want,You can add exceptions for files to not report issues on certain files in your source code analyzer. This might be helpful. https://stackoverflow.com/questions/32272138/suppressing-violations-in-pmd – Shubham Anand Feb 09 '21 at 08:51
  • I can do this way @SuppressWarnings("PMD") But instead of PMD i am looking for specific Code related to this error..... This @SuppressWarnings("PMD") works but i want specific code. – priyanka Feb 09 '21 at 08:59
  • Well Looks like we are missing the context as to what exactly you are looking for. Please try updating the question with more details. – Shubham Anand Feb 09 '21 at 09:05
  • So PMD is issuing you a warning saying that the field _could_ be final. It is up to you as a developer if in this instance PMD is correct. I would argue that in this instance PMD is correct that your real issue is you are attempting to mock something you shouldnt be mocking (HTTPClient), as Shubham says try using something like WireMock that is a much better way to test a class that makes calls on the network, means you will have better structured code and the PMD warning will go away because the field is final. Everything solved. – Gavin Feb 09 '21 at 09:48
0

A simple way to use a mocked HttpClient for your tests is to add a second constructor that takes a HttpClient.

public class KMSHttpClientImpl implements KMSHttpClient
{

  private final HttpClient httpClient;

  public KMSHttpClientImpl ()
  {
    final PoolingHttpClientConnectionManager connectionManager =
            kmsHttpConnectionManager.getConnectionManager();
    httpClient = HttpClients.custom()
            .setConnectionManager(connectionManager)
            .build();
  }

  // This constructor is package private instead
  // of public so it is not accidentally used by
  // classes outside of this package. If your test
  // class is not in the same package, then you
  // need to make this a public constructor.
  KMSHttpClientImpl (final HttpClient httpClient)
  {
    this.httpClient = httpClient;
  }

}

You then inject your mocked HttpClient using this constructor and will need neither @InjectMocks nor @Mock in your tests.

@Test
public void testPostRequest () throws KMSClientException
{
  final HttpClient httpClient = Mockito.mock(HttpClient.class);
  final HttpResponse response = prepareResponse(HttpStatus.SC_OK);
  Mockito.when(httpClient.execute(Mockito.any())).thenReturn(response);
  final KMSHttpClientImpl kmsHttpClientImpl = new KMSHttpClientImpl(httpClient);

  // run your test...
}
Acanda
  • 672
  • 5
  • 12
0

Apache HTTP client 5 deprecates the single argument HttpClient.execute() method in favour of the methods that take a ResponseHandler so that the library can ensure that the request is cleaned up.

To mock those calls, do this:

// mock the http client
CloseableHttpClient httpClient = mock(CloseableHttpClient.class);

// mock the response object
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
when(response.getCode()).thenReturn(200);
// [...] add other mocks, e.g. response headers and body entity

// provide the mock response object when your mock HttpClient is executed
// replace any() with more specific matchers if you need to

when(httpClient.execute(any(ClassicHttpRequest.class), any(HttpClientResponseHandler.class))).thenAnswer(invocation ->
  invocation.getArgument(1, HttpClientResponseHandler.class).handleResponse(response));
Andy Brown
  • 11,766
  • 2
  • 42
  • 61
-1

You can't mock a final instance using plain mocking. You need something like PowerMock.

See the answer to this questionfor implementation.

TrueDub
  • 5,000
  • 1
  • 27
  • 33
  • I don't want to use PowerMock. Is there a way to suppress this PMD warning Private field 'httpClient' could be made final; it is only initialized in the declaration or constructor – priyanka Feb 09 '21 at 08:41
  • To suppress a PMD warning, use an annotation - see https://pmd.github.io/latest/pmd_userdocs_suppressing_warnings.html. – TrueDub Feb 09 '21 at 08:49
  • PowerMock is a hack, in this instance at least. The approach to testing is the issue, `HTTPClient` shouldnt be being mocked at all, an alternative approach is what is required. – Gavin Feb 09 '21 at 09:51