I want to write unit test for a method which uses Apache Http to fetch data.
public UserResponse getUserById(String id) {
UserResponse userResponse;
String jwtToken = SecurityUtil.getAuthToken();
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpGet httpGet = new HttpGet("%s/users/id/%s".formatted(apiUrl, id));
httpGet.setHeader("Authorization", "Bearer " + jwtToken);
httpGet.setHeader("Content-Type", "application/json");
logger.info("network call");
HttpResponse response = httpClient.execute(httpGet);
int statusCode = response.getStatusLine().getStatusCode();
if (response.getStatusLine().getStatusCode() == 200) {
String responseString = EntityUtils.toString(response.getEntity());
ObjectMapper mapper = new ObjectMapper();
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
cloneLoanUserResponse = mapper.readValue(responseString, CloneLoanUserResponse.class);
} else {
throw new RuntimeException("API call failed with status code: " + statusCode);
}
} catch (IOException ex) {
throw new RuntimeException("Error calling getUserByFinuId due to " + ex);
}
return userResponse;
}
Here is the test case that I have managed courtesy chatgpt.
@Test
public void shouldReturnUserById() throws IOException {
UserResponse expectedResponse = new UserResponse(UUID.fromString("ea9a2ace-7872-4c20-ac7f-37d27b5b657b"), "63b2c4f1c3796b6fc01kebab", "1002");
CloseableHttpClient httpClientMock = Mockito.mock(CloseableHttpClient.class);
CloseableHttpResponse httpResponseMock = Mockito.mock(CloseableHttpResponse.class);
HttpEntity httpEntityMock = mock(HttpEntity.class);
StatusLine statusLineMock = mock(StatusLine.class);
when(httpClientMock.execute(any(HttpGet.class))).thenReturn(httpResponseMock);
when(httpResponseMock.getStatusLine()).thenReturn(statusLineMock);
when(statusLineMock.getStatusCode()).thenReturn(200);
when(httpResponseMock.getEntity()).thenReturn(httpEntityMock);
when(httpEntityMock.getContent()).thenReturn(new ByteArrayInputStream("{\n \"userId\": \"ea9a2ace-7872-4c20-ac7f-37d27b5b657b\",\n \"legacyId\": \"645606f1bd069d25b6d0ce59\",\n \"finuId\": \"645606f1bd069d25b6d0ce59\"\n}".getBytes()));
// Mock ObjectMapper.readValue()
ObjectMapper objectMapperMock = mock(ObjectMapper.class);
when(objectMapperMock.readValue(anyString(), eq(CloneLoanUserResponse.class))).thenReturn(expectedResponse);
try {
when(httpClientMock.execute(any(HttpGet.class))).thenReturn(httpResponseMock);
} catch (IOException e) {
e.printStackTrace();
}
// When
CloneLoanUserResponse actualResponse = userServiceClient.getUserByFinuId("FINUA0001002");
// Then
// Add assertions based on your expected and actual results
assertEquals(expectedResponse, actualResponse);
}
When I try to run above test case, it makes an http call on a random port but fails to return anything and complete / exit the test.
I am very new to Java and Junit. Help is appreciated.
Your mocking is unfortunately not used, because the method to test creates its own
CloseableHttpClient. Fortunately, HttpClient has something for this. With these, you can send actual HTTP requests, but to a test server that only exists while each test is running.HttpClient 4.x
Since the tags include "apache-httpclient-4.x" I'll that with that.
First you need a dependency with the same version as the HttpClient you're using:
This gives you access to class
LocalServerTestBase. This is meant to be extended by your own test classes. If that's not possible you need to create a little helper class that exposes a protected field you need:If you're using JUnit 4, the
LocalServerTestBasecomes with its own@Beforeand@Aftermethods. If you're using JUnit 5 you need to call these explicitly:In your test methods you can now register a handler that determines the response:
After that's done, you must call
start()(orserver.start()). That returns anHttpHostthat you can use to get its port etc. It has methodtoURIthat gives you aURIif you prefer that.Now that you have that, you can set the
apiUrlof youruserServiceClient.If you need the host for the
apiUrlin your existing@BeforeEachmethod, you can move some stuff around so you can callserver.start()there. That does mean setting up the handler in your@BeforeEachmethod. That makes it less flexible.HttpClient 5.x
HttpClient 5.x has something similar. You again need a dependency with the same version as the HttpClient you're using:
Unlike HttpClient 4'x
LocalServerTestBase, HttpClient 5'sClassicTestServerwas always meant to be included. And unlikelocalServerTestBase, you can register handlers after starting. That makes it simpler to use.Setup this server in your test class:
In your test methods you can now register a handler that determines the response: