JUnit 5: Mocking the HttpClient in Java

Unfortunately when building AEM backend logic, the org.apache.http.client.HttpClient is non-trivial to write unit tests. In my case, I scavenged across the net to find a solution where you can make an GET and POST request via HttpClient in particular for AEM, and unit test is performing as expected. In order for me write successful to my servlet, I had to refactor the code. Forcefully, I had to change the org.apache.http.client.HttpClient to org.apache.http.osgi.services.HttpClientBuilderFactory OSGI service; the HttpClientBuilderFactor giving me the HttpClient that I need.

This article will showcase a successful unit test with the doGet and doPost example code. The result of the methods will return a JSON string, which we will mock.


1. Dependencies

Before we begin, I would like to mention the dependencies that I am using to get this code to work (please make sure you are using JUNIT5):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<dependency>
  <groupId>io.wcm</groupId>
  <artifactId>io.wcm.testing.aem-mock.junit5</artifactId>
  <exclusions>
    <exclusion>
      <groupId>org.apache.sling</groupId>
      <artifactId>org.apache.sling.models.impl</artifactId>
    </exclusion>
    <exclusion>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-simple</artifactId>
    </exclusion>
  </exclusions>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-core</artifactId>
  <version>4.1.0</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-junit-jupiter</artifactId>
  <version>4.1.0</version>
  <scope>test</scope>
</dependency>

2. HttpClientServlet.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package com.sourcedcode.core.servlets;

import com.adobe.granite.rest.Constants;
import com.drew.lang.annotations.NotNull;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.osgi.services.HttpClientBuilderFactory;
import org.apache.http.util.EntityUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.servlets.annotations.SlingServletResourceTypes;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

@Component(service = Servlet.class)
@SlingServletResourceTypes(
        methods = {HttpConstants.METHOD_GET, HttpConstants.METHOD_POST},
        resourceTypes = "sourcedcode/component",
        selectors = {"example"},
        extensions = {"json"}
)
public class HttpClientServlet extends SlingAllMethodsServlet {

    @Reference
    private HttpClientBuilderFactory clientBuilderFactory;

    @Override
    protected void doGet(@NotNull SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response) throws ServletException, IOException {
        CloseableHttpClient client = clientBuilderFactory.newBuilder().build();
        HttpGet getMethod = new HttpGet("https://dummy.restapiexample.com/api/v1/employee/1");
        CloseableHttpResponse httpClientResponse = client.execute(getMethod);
        String responseBody = EntityUtils.toString(httpClientResponse.getEntity());
        JsonObject jsonObject = new Gson().fromJson(responseBody, JsonObject.class);
        response.setStatus(HttpServletResponse.SC_OK);
        response.setContentType(Constants.CT_JSON);
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.getWriter().write(jsonObject.toString());
    }

    @Override
    protected void doPost(@NotNull SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response) throws ServletException, IOException {
        CloseableHttpClient client = clientBuilderFactory.newBuilder().build();
        ObjectMapper mapper = new ObjectMapper();
        Map<String, String> object = new HashMap<>();
        object.put("test", "value");
        StringEntity requestData = new StringEntity(
                mapper.writeValueAsString(object),
                ContentType.APPLICATION_JSON);
        HttpPost postMethod = new HttpPost("https://freemeposting.free.beeceptor.com/my/api/path");
        postMethod.setEntity(requestData);
        postMethod.setHeader("Test", "Test");
        CloseableHttpResponse preRenderedResponse = client.execute(postMethod);
        String responseBody = EntityUtils.toString(preRenderedResponse.getEntity());
        JsonObject jsonObject = new Gson().fromJson(responseBody, JsonObject.class);
        response.setStatus(HttpServletResponse.SC_OK);
        response.setContentType(Constants.CT_JSON);
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.getWriter().write(jsonObject.toString());
    }
}

3. HttpClientServletTest.java

In all, the most important line here that you should take a look at is line:60. On this line you can see that I am injecting mocks into the HttpClientBuilderFactory osgi service, which when clientBuilderFactory.newBuilder().build() is being called, the HttpClientBuilderFactory is bring returned in lin:47, which will be the HttpClient itself.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package com.sourcedcode.core.servlets;

import io.wcm.testing.mock.aem.junit5.AemContext;
import io.wcm.testing.mock.aem.junit5.AemContextExtension;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.osgi.services.HttpClientBuilderFactory;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.testing.mock.sling.ResourceResolverType;
import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import javax.servlet.ServletException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

@ExtendWith({AemContextExtension.class, MockitoExtension.class})
public class HttpClientServletTest extends SlingAllMethodsServlet {

    private final AemContext aemContext = new AemContext(ResourceResolverType.JCR_MOCK);

    @Mock
    HttpClientBuilderFactory clientBuilderFactory;

    @Mock
    CloseableHttpClient closeableHttpClientMock;

    @Mock
    CloseableHttpResponse closeableHttpResponseMock;

    @InjectMocks
    private HttpClientServlet unitTest = new HttpClientServlet();

    @Test
    public void testServletDoGet() throws ServletException, IOException {
        String responseData = "{status: "success"}";
        InputStream anyInputStream = new ByteArrayInputStream(responseData.getBytes());


        MockSlingHttpServletRequest request = aemContext.request();
        SlingHttpServletResponse response = mock(SlingHttpServletResponse.class);
        HttpEntity httpEntityMock =  mock(HttpEntity.class);

        when(clientBuilderFactory.newBuilder()).thenReturn(mock(HttpClientBuilder.class));
        when(clientBuilderFactory.newBuilder().build()).thenReturn(closeableHttpClientMock);
        when(closeableHttpClientMock.execute(any())).thenReturn(closeableHttpResponseMock);
        when(closeableHttpResponseMock.getEntity()).thenReturn(httpEntityMock);
        when(httpEntityMock.getContent()).thenReturn(anyInputStream);
        PrintWriter printWriterMock = mock(PrintWriter.class);
        when(response.getWriter()).thenReturn(printWriterMock);

        aemContext.registerService(HttpClientBuilderFactory.class, clientBuilderFactory);

        unitTest.doGet(request, response);
        verify(printWriterMock).write(eq("{"status":"success"}"));

    }

    @Test
    public void testServletDoPost() throws ServletException, IOException {
        String responseData = "{status: "success"}";
        InputStream anyInputStream = new ByteArrayInputStream(responseData.getBytes());

        MockSlingHttpServletRequest request = aemContext.request();
        SlingHttpServletResponse response = mock(SlingHttpServletResponse.class);
        HttpEntity httpEntityMock =  mock(HttpEntity.class);

        when(clientBuilderFactory.newBuilder()).thenReturn(mock(HttpClientBuilder.class));
        when(clientBuilderFactory.newBuilder().build()).thenReturn(closeableHttpClientMock);
        when(closeableHttpClientMock.execute(any())).thenReturn(closeableHttpResponseMock);
        when(closeableHttpResponseMock.getEntity()).thenReturn(httpEntityMock);
        when(httpEntityMock.getContent()).thenReturn(anyInputStream);
        PrintWriter printWriterMock = mock(PrintWriter.class);
        when(response.getWriter()).thenReturn(printWriterMock);

        aemContext.registerService(HttpClientBuilderFactory.class, clientBuilderFactory);

        unitTest.doPost(request, response);
        verify(printWriterMock).write(eq("{"status":"success"}"));
    }
}

Looking for Help? Or need a mentor?

If you need help to super drive your AEM career road map, you can hire me for a one-hour session on codementor.io; We can work together on a price works just for you. https://www.codementor.io/@briankasingli. I'd be happy to help you along your way to becoming a high-quality AEM full-stack engineer.

Was this post helpful?

Hello, I am an enthusiastic Adobe Community Advisor and a seasoned Lead AEM Developer. I am currently serving as an AEM Technical Lead at MNPDigital.ca, bringing over a decade of extensive web engineering experience and more than eight years of practical AEM experience to the table. My goal is to give back to the AEM Full Stack Development community by sharing my wealth of knowledge with others. You can connect with me on LinkedIn.

4 thoughts on “JUnit 5: Mocking the HttpClient in Java

Leave a Reply

Your email address will not be published. Required fields are marked *

Back To Top