46

I'm trying to mock Apache HttpClient Interface in order to mock one of its methods mentioned below to return a stubbed JSON object in response.

HttpResponse response = defaultHttpClient.execute(postRequest); 

Could somebody be able to suggest how to achieve this with some sample code? Your help would be greatly appreciated.

Thanks

Chris
  • 5,040
  • 3
  • 20
  • 24
Global Dictator
  • 1,539
  • 9
  • 24
  • 37

5 Answers5

32

Here is what I did to test my code using Mockito and Apache HttpBuilder:

Class under test:

import java.io.BufferedReader;
import java.io.IOException;

import javax.ws.rs.core.Response.Status;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClientBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class StatusApiClient {
private static final Logger LOG = LoggerFactory.getLogger(StatusApiClient.class);

    private String targetUrl = "";
    private HttpClient client = null;
    HttpGet httpGet = null;

    public StatusApiClient(HttpClient client, HttpGet httpGet) {
        this.client = client;
        this.httpGet = httpGet;
    }

    public StatusApiClient(String targetUrl) {
        this.targetUrl = targetUrl;
        this.client = HttpClientBuilder.create().build();
        this.httpGet = new HttpGet(targetUrl);
    }

    public boolean getStatus() {
        BufferedReader rd = null;
        boolean status = false;
        try{
            LOG.debug("Requesting status: " + targetUrl);


            HttpResponse response = client.execute(httpGet);

            if(response.getStatusLine().getStatusCode() == Status.OK.getStatusCode()) {
                LOG.debug("Is online.");
                status = true;
            }

        } catch(Exception e) {
            LOG.error("Error getting the status", e);
        } finally {
            if (rd != null) {
                try{
                    rd.close();
                } catch (IOException ioe) {
                    LOG.error("Error while closing the Buffered Reader used for reading the status", ioe);
                }
            }   
        }

        return status;
    }
}

Test:

import java.io.IOException;

import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.HttpHostConnectException;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;

public class StatusApiClientTest extends Mockito {

    @Test
    public void should_return_true_if_the_status_api_works_properly() throws ClientProtocolException, IOException {
        //given:
        HttpClient httpClient = mock(HttpClient.class);
        HttpGet httpGet = mock(HttpGet.class);
        HttpResponse httpResponse = mock(HttpResponse.class);
        StatusLine statusLine = mock(StatusLine.class);

        //and:
        when(statusLine.getStatusCode()).thenReturn(200);
        when(httpResponse.getStatusLine()).thenReturn(statusLine);
        when(httpClient.execute(httpGet)).thenReturn(httpResponse);

        //and:
        StatusApiClient client = new StatusApiClient(httpClient, httpGet);

        //when:
        boolean status = client.getStatus();

        //then:
        Assert.assertTrue(status);
    }

    @Test
    public void should_return_false_if_status_api_do_not_respond() throws ClientProtocolException, IOException {
        //given:
        HttpClient httpClient = mock(HttpClient.class);
        HttpGet httpGet = mock(HttpGet.class);
        HttpResponse httpResponse = mock(HttpResponse.class);
        StatusLine statusLine = mock(StatusLine.class);

        //and:
        when(httpClient.execute(httpGet)).thenThrow(HttpHostConnectException.class);

        //and:
        StatusApiClient client = new StatusApiClient(httpClient, httpGet);

        //when:
        boolean status = client.getStatus();

        //then:
        Assert.assertFalse(status);
    }

}

What do you think folks, do I need to improve something? (Yeah, I know, the comments. That is something I brought from my Spock background :D)

user1855042
  • 621
  • 6
  • 5
  • This is good, but is there a way to do this where your mocks do not need to be so implementation-specific? For instance, so that the test would still work if we switched out our HTTP Client for something with a different interface? Ideally, just watching for an HTTP Request going out (and spying on things like URL, headers, payload) and the ability to control the response coming back (and spying on things like payload, headers)? I'm coming from a JavaScript testing background where this is the pattern -- mocking the backend at the browser level instead of library level. – oooyaya Jan 31 '19 at 16:04
  • When I mock the execute method, it actually gets called and I get a NullPointerException. Could you help me with this? – Amber Jan 28 '20 at 10:12
10

In your unit test class you need to mock defaultHttpClient:

@Mock
private HttpClient defaultHttpClient;

Then you tell mockito (for example in @Before method) to actually create your mocks by:

MockitoAnnotations.initMocks(YourTestClass);

Then in your test method you define what execute() method should return:

when(defaultHttpClient.execute(any()/* or wahtever you want here */)).thenReturn(stubbed JSON object);
Flying Dumpling
  • 1,294
  • 1
  • 11
  • 13
  • Thanks thats brilliant! But what I also needed to know was whether I should be mocking DefaultHttpClient or HttpClient instead? – Global Dictator Dec 12 '13 at 12:43
  • 1
    In mockito you can mock all: interfaces, abstract classes, normal classes. In your case, if you want to simulate the behaviour of only `execute()` method you can mock the `HttpClient` interface. But if you would like do simulate methods from `DefaultHttpClient` that aren't available in `HttpClient` you have to mock the `DefaultHttpClient` class directly. – Flying Dumpling Dec 12 '13 at 14:08
  • Hi, I'm trying to mock the following: – Global Dictator Dec 13 '13 at 09:41
  • HttpResponse response = defaultHttpClient.execute(postRequest); BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent())); StringBuffer result = new StringBuffer(); while ((line = rd.readLine()) != null) { result.append(line); } JSONObject jsonResponseObject = new JSONObject(result.toString()); – Global Dictator Dec 13 '13 at 09:43
  • I have a stubbed JSON OBject but which other mocs do I need to create for the above mentioned calls? – Global Dictator Dec 13 '13 at 09:57
  • For clarification. You can't mock local varaibles with `@Mock` annotation. `@Mock` allows you to mock only fields of the class you are testing. To mock local variables you can use PowerMockito. See http://stackoverflow.com/questions/6520242/mocking-methods-of-local-scope-objects-with-mockito – Flying Dumpling Dec 13 '13 at 10:35
  • 15
    -1. httpClient.execute() does not return a JSON object, but a response object and you need to extract the JSON body from it which is why mocking it is not straight forward. – Bastian Voigt Feb 11 '16 at 14:19
  • My defaultHttpClient is null? – powder366 Sep 13 '19 at 14:24
10

There is a nicer way to do this without having to add PowerMock as yet another dependency. Here you only need an extra constructor taking HTTPClient as an argument and Mockito. In this example I'm creating a custom health check (Spring Actuator) and I need to mock the HTTPClient for unit testing.

Libs: JUnit 5, Spring Boot 2.1.2 and Mockito 2.

Component:

@Component
public class MyHealthCheck extends AbstractHealthIndicator {

    HttpClient httpClient;

    public MyHealthCheck() { 
        httpClient = HttpClientBuilder.create().build();
    }

    /** 
    Added another constructor to the class with an HttpClient argument.
    This one can be used for testing
    */ 
    public MyHealthCheck(HttpClient httpClient) { 
        this.httpClient = httpClient; 
    }

    /**
    Method to test 
    */ 
    @Override
    protected void doHealthCheck(Builder builder) throws Exception {

        //
        // Execute request and get status code
        HttpGet request = new HttpGet("http://www.SomeAuthEndpoint.com");
        HttpResponse response = httpClient.execute(request);

        //
        // Update builder according to status code
        int statusCode = response.getStatusLine().getStatusCode();
        if(statusCode == 200 || statusCode == 401) {
            builder.up().withDetail("Code from service", statusCode);
        } else {
            builder.unknown().withDetail("Code from service", statusCode);
        }
    }
}

Test method:

Note that here we use Mockito.any(HttpGet.class)

private static HttpClient httpClient;
private static HttpResponse httpResponse;
private static StatusLine statusLine;

@BeforeAll
public static void init() {
    //
    // Given
    httpClient = Mockito.mock(HttpClient.class);
    httpResponse = Mockito.mock(HttpResponse.class);
    statusLine = Mockito.mock(StatusLine.class);
}


@Test
public void doHealthCheck_endReturns401_shouldReturnUp() throws Exception {

    //
    // When
    when(statusLine.getStatusCode()).thenReturn(401);
    when(httpResponse.getStatusLine()).thenReturn(statusLine);
    when(httpClient.execute(Mockito.any(HttpGet.class))).thenReturn(httpResponse);

    //
    // Then
    MyHealthCheck myHealthCheck = new MyHealthCheck(httpClient);
    Health.Builder builder = new Health.Builder();
    myHealthCheck.doHealthCheck(builder);
    Status status = builder.build().getStatus();
    Assertions.assertTrue(Status.UP == status);
}
gnulli
  • 101
  • 2
  • 4
  • I tried this but didn't get it working. This fails at the step where the instance of HttpGet is created since it is not mocked. How would it work? – Andy Dufresne Feb 08 '22 at 14:23
7

You can look at HttpClientMock, I wrote it for internal project but later decided to open source. It allows you to define mock behavior with fluent API and later verify a number of made calls. Example:

HttpClientMock httpClientMock = new 
HttpClientMock("http://localhost:8080");
httpClientMock.onGet("/login?user=john").doReturnJSON("{permission:1}");

httpClientMock.verify().get("/login?user=john").called();
Paweł Adamski
  • 3,285
  • 2
  • 28
  • 48
  • 2
    That looks very nice, but how do you make sure that the class under test uses your `HttpClientMock` instead of `HttpClients.createDefault()`? – neXus Mar 09 '18 at 14:13
  • Well, it can look very different between applications. In my apps usually I have a separate test Configuration which injects HttpClientMock into application context. – Paweł Adamski Mar 11 '18 at 13:15
1

You can do this easily using PowerMockito which can also mock final/static methods, private methods and anonymous classes easily. Here's the sample code for mocking http request. JSON_STRING_DATA is any string which you want to get from execute method.

PowerMockito.mockStatic(DefaultHttpClient.class);
    HttpClient defaultHttpClientMocked =  PowerMockito.mock(DefaultHttpClient.class);        
    PowerMockito.when(defaultHttpClientMocked.execute(Mockito.any(HttpPost.class))).thenReturn(createMockedHTTPResponse(JSON_STRING_DATA));
Hussain Mansoor
  • 2,934
  • 2
  • 27
  • 40
  • 7
    Let's make a bet: 3 years from now PowerMockito will no longer be in vogue and any tests that use it will be "legacy" tests (probably @Ignore'd, or a burden to maintain, or your team will be trying to migrate off it). Anyone please respond to me in July 2021 if I am wrong and I will eat humble pie. – Sridhar Sarnobat Jul 18 '18 at 05:15
  • 2
    @SridharSarnobat In July 2021, you can do the same using Mockito directly: https://stackoverflow.com/a/63242611/248304 :) – angelcervera Jan 11 '22 at 18:11
  • 1
    @SridharSarnobat if we base on the commits on https://github.com/powermock/powermock you won :-) – aled Oct 12 '22 at 20:59
  • 1
    Thanks for circling back :) Moral of the story: use discretion when adding dependencies to your project. – Sridhar Sarnobat Oct 13 '22 at 21:13