0

I am trying to write junit 5 code in a java maven project(not a springboot project) , which is calling an API and extracting response and creating a file. But when I tried to mock the API calling, junit still calls actual API, which is not the desired behaviour. Can someone help to identify issue here?

I followed this link to write below unit tests How to mock HttpClient's send method in junit 5?

Actual service code -

public class MyService {

    public void create(String apiHost, String firstPage) {
        HttpClient httpClient = HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_2)
                .connectTimeout(Duration.ofSeconds(30))
                .build();
        HttpRequest httpRequest = getHttpRequest(apiHost, firstPage);
 
        MyResponse myResponse = invokeApi(httpClient, httpRequest);
        List<ResponseData> responseDataList = new ArrayList<>(myResponse.getData());
        while (null != myResponse.getLinks().getNext()) {

            myResponse = invokeApi(httpClient, getHttpRequest(apiHost, invokeApi.getLinks().getNext()));
            locationDetailsList.addAll(invokeApi.getData());
        }

        createFile(fileName, responseDataList);
    }

    private HttpRequest getHttpRequest(String apiHost, String pageUri) {
        return HttpRequest.newBuilder()
                .header("Content-Type", "application/json")
                .GET()
                .uri(URI.create(apiHost + pageUri))
                .build();
    }

    @SneakyThrows
    MyResponse invokeApi(HttpClient httpClient, HttpRequest httpRequest) {
        HttpResponse<Supplier<MyResponse>> result = httpClient.send(httpRequest, new JsonBodyHandler<>(MyResponse.class));
        return result.body().get();
    }

    @SneakyThrows
    void createFile(String fileName, List<ResponseData> responseDataList) {
        Path filePath = Paths.get("test/path" + fileName);
        Files.createFile(filePath);
        Files.writeString(filePath, header + System.lineSeparator(), StandardOpenOption.APPEND);
        for (ResponseData responseData : responseDataList) {
            Files.writeString(filePath, responseData + System.lineSeparator(), StandardOpenOption.APPEND);
        }
    }

Junit 5 code -

@ExtendWith(MockitoExtension.class)
class MyServiceTests {

    @InjectMocks
    private MyService myService;

    @Mock
    private HttpClient httpClient;
    @Mock
    HttpResponse<Object> httpResponse;

    @Mock
    private HttpRequest request;

    @Mock
    Supplier<MyResponse> myResponseSupplier;


    @Test
    void testSendMessage() throws Exception {

        when(httpClient.send(any(),any())).thenReturn(httpResponse);
        when(httpResponse.body()).thenReturn(myResponseSupplier);
        when(myResponseSupplier.get()).thenReturn(getMyResponse());
        myService.create("https://www.google.com", "/v1/test?page_size=100");
        verify(httpClient).send(request, HttpResponse.BodyHandlers.ofString());
    }
}

In the above junit code when "myService.createmyMnt" is executed it actually tried to invoke https://test.com instead of mocking the call.

springenthusiast
  • 403
  • 1
  • 8
  • 30

1 Answers1

2

You are facing this because you are mocking the same dependencies 2 times. Here what i mean:

 @Mock
 private HttpClient httpClient;

And then in beforeEach you again are mocking your dependency

httpClient = mock(HttpClient.class);

I recommend choosing the approach that you need and don't mock 2 times. Here where you are mocking and use @InjectMocks. Also we need to add annotation @ExtendWith(MockitoExtension.class) this will handle initMocks(this) and we won't need to add it(this annotation helps to mocks be properly injected).

@ExtendWith(MockitoExtension.class)
class MyServiceTests {

    @InjectMocks
    private MyService myService;

    @Mock
    private HttpClient httpClient;
    @Mock
    HttpResponse<Object> httpResponse;

    @Mock
    private HttpRequest request;

    @Test
    void testSendMessage() throws Exception {
        MyResponse myResponse=new MyResponse();

        when(httpClient.send(any(),any())).thenReturn(httpResponse);
        when(myService.invokeApi(httpClient,request)).thenReturn(myResponse);
        myService.createmyMnt("https://test.com", "/v1/test?page_size=100");
        verify(httpClient).send(request, HttpResponse.BodyHandlers.ofString());
    }
}

or use constructor injection like:

class MyServiceTests {

    private MyService myService;
    private HttpClient httpClient;
    private HttpResponse<Supplier<MyResponse>> httpResponse;
    private HttpRequest request;

    @BeforeEach
    void setUp() {
        httpClient = mock(HttpClient.class);
        httpResponse = mock(HttpResponse.class);
        request = mock(HttpRequest.class);
        myService = new MyService();
    }

    @Test
    void testSendMessage() throws Exception {
        MyResponse myResponse = new MyResponse();

        when(httpClient.send(any(), any())).thenReturn(httpResponse);
        when(httpResponse.body()).thenReturn(() -> myResponse);

        myService.create("https://test.com", "/v1/test?page_size=100");

        verify(httpClient).send(request, new JsonBodyHandler<>(MyResponse.class));
    }
}

According to the comments try this please:

    @ExtendWith(MockitoExtension.class)
class MyServiceTests {

    @InjectMocks
    private MyService myService;

    @Mock
    private HttpClient httpClient;

    @Mock
    private HttpResponse<Supplier<MyResponse>> httpResponse;

    @Mock
    private HttpRequest request;

    @Mock
    private Supplier<MyResponse> myResponseSupplier;

    @Test
    void testSendMessage() throws Exception {
        MyResponse myResponse = getMyResponse();

        when(httpClient.send(any(), any())).thenReturn(httpResponse);
        when(httpResponse.body()).thenReturn(() -> myResponseSupplier);
        when(myResponseSupplier.get()).thenReturn(myResponse);

        myService.create("https://www.google.com", "/v1/test?page_size=100");

        verify(httpClient).send(request, HttpResponse.BodyHandlers.ofString());
      
    }

    private MyResponse getMyResponse() {
        return new MyResponse();
    }
}
Feel free
  • 758
  • 5
  • 15
  • I did remove mocking in 2 places as you suggested but getting nullpointer in the line "return result.body().get()" of method invokeApi , because "java.net.http.HttpResponse.body()" is null – springenthusiast May 22 '23 at 07:14
  • 1
    Congrats! You have new exception and you can go forward. You need to stub HttpResponse.body() with when() using powermockito or mockito-inline(which i suggest) because it`s static method. – Feel free May 22 '23 at 07:16
  • Thanks, you are right I missed stubbing HttpResponse.body(). But I still notice that it's actually calling the API instead of mocking , if I provide any dummy URL instead of actual API it gives "java.net.ConnectException". I wanted it to mock the API calling instead of calling actual API. – springenthusiast May 22 '23 at 07:23
  • Maybe you had a typo with createmyMnt() and you need to write create(). seems strange to me – Feel free May 22 '23 at 07:35
  • That was typo while posting question on here, updated the code to fix typo's. Still code is failing at "return result.body().get();" because of jackson parsing exception when code executes supplier.get() method, it's giving 404 response when supplier.get is executed and jackson parse fails due to invalid html content of 404 response – springenthusiast May 22 '23 at 09:45
  • Note: You mocked HttpResponse httpResponse; but in your method you are using HttpResponse> httpResponse; so you need to pass supplier when(httpResponse.body()).thenReturn(() -> myResponse); – Feel free May 22 '23 at 10:10
  • Which I am already doing if you see my updated code above, I am returning a mocked supplier. So the main issue I mentioned in code still exists, the junit is calling actual API instead of mocking the behaviour and along with that now it's failing to get response from supplier also – springenthusiast May 22 '23 at 10:26
  • Try my last *updated* version of the code. If it won't work. I can`t help with this. because code is not reproducible and i can not debug this – Feel free May 22 '23 at 10:32
  • Thank you for your assistance, it didn't work. It's still trying to connect to google.com and supplier.get fails to parse – springenthusiast May 22 '23 at 11:03