0

I am calling an external API using HttpClient as below,

   HttpClient client = HttpClient.newHttpClient();
   HttpRequest request = HttpRequest.newBuilder().uri(URI.create("http://localhost:8080/api"))
                    .POST(BodyPublishers.ofString(requestBody)).header("Authorization", 
                           authorizationHeader)
                    .header("Content-Type", "application/json").build();

   HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
   LOGGER.debug(response.body());

I have tried solution from How to mock HttpClient's send method in junit 5?. This actually does not have information on how to return the expected response in stubbing statement.

I am basically looking to stub below statement so that I can test the expected and actual result without invoking the real API,

    client.send(request, HttpResponse.BodyHandlers.ofString()); 

The real API call returns a json in String format, which I will then be mapping to an entity and use it further.

Could someone please put your thoughts on this. Thanks in advance.

BSM
  • 173
  • 3
  • 13
  • You can't stub `HttpClient.send` because you are creating it in the method you are trying to test. You need to create a factory object which you can mock to return a mock `HttpClient`. – tgdavies Mar 24 '23 at 00:43
  • I am not sure which factory are you talking about. Here in my case it is httpclient is from java.net package. I didn't find any factory class to build httpclient in Java. And you are suggesting to change the source code? – BSM Mar 24 '23 at 02:23
  • FWIW there's an example of an offline HttpClient that replies with fixed canned responses here: https://github.com/openjdk/jdk/tree/master/test/jdk/java/net/httpclient/offline – daniel Mar 24 '23 at 17:45

3 Answers3

1

The problem with your test is that as the HttpClient instance is created inside the method you are testing, you can't mock it. You need to change your code to be something like the code below.

You would probably want to extend the test to make some more assertions about the parameters passed to send, and to test your handling of the response (which isn't shown in your question), but I think this answer explains how to mock the client successfully.

package com.example.demo;

import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

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

class MyClass {
    private final HttpClientFactory httpClientFactory;

    MyClass(HttpClientFactory httpClientFactory) {
        this.httpClientFactory = httpClientFactory;
    }

    public HttpResponse<String> myMethod(String requestBody) throws IOException, InterruptedException {

        HttpClient client = httpClientFactory.create();
        HttpRequest request = HttpRequest.newBuilder().uri(URI.create("http://localhost:8080/api"))
                .POST(HttpRequest.BodyPublishers.ofString(requestBody))
                .header("Content-Type", "application/json").build();

        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        return response;
    }
}

class HttpClientFactory {
    public HttpClient create() {
        return HttpClient.newHttpClient();
    }
}

public class MyTest {


    @Test
    public void aTest() throws IOException, InterruptedException {
        HttpClientFactory mockFactory = mock(HttpClientFactory.class);
        HttpClient mockClient = mock(HttpClient.class);
        HttpResponse mockResponse = mock(HttpResponse.class);
        when(mockFactory.create()).thenReturn(mockClient);
        when(mockClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandler.class)))
                .thenReturn(mockResponse);
        MyClass classToTest = new MyClass(mockFactory);
        classToTest.myMethod("the request body");
        verify(mockClient).send(argThat(request -> request.uri().equals(URI.create("http://localhost:8080/api"))), any());
    }
}


tgdavies
  • 10,307
  • 4
  • 35
  • 40
  • Thank you @tgdavies. Factory pattern worked. I had to change the source code, but it had to be changed when it was not "testable". – BSM Mar 24 '23 at 05:38
  • Yes, code not written to be testable has to be changed. – tgdavies Mar 24 '23 at 05:57
0

You can try to do it like:

when(client.send(request, HttpResponse.BodyHandlers.ofString()).thenReturn(response)

or

 when(client.send(any(), anyString()).thenReturn(response)

The response could be the mock object or you can create response whatever you want for it

and then you can test assertEquals((expectedResult, yourmethod())

Feel free
  • 758
  • 5
  • 15
  • The second parameter to `send` is not a `String`. – tgdavies Mar 24 '23 at 00:41
  • @trying my best, as tgdavies pointed out the parameters are wrong in your answer. It could be client.send(any(HttpRequest.class), any(BodyHandler.class)).thenReturn(response). I have tried this though, it doesn't work and also I am not sure how to return a response. I want to return a response with a string value set to it. – BSM Mar 24 '23 at 02:16
0

I am calling an external API using HttpClient as below,

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder().uri(URI.create("http://localhost:8080/api"))
    .POST(BodyPublishers.ofString(requestBody)).header("Authorization", 
                           authorizationHeader)
    .header("Content-Type", "application/json").build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
Robert
  • 7,394
  • 40
  • 45
  • 64
SherOFF
  • 1
  • 1