I am trying to test HttpClient using Mockito and this article. I am receiving the error below, and not sure how to fix. The error is below. I am following the contents of article very similarly. Its failing on CloseableHttpResponse closeableHttpResponse = client.execute(httpPost)
, when I already mocked it.
Resource: Mocking Apache HTTPClient using Mockito
Main Code:
public class ProductService {
private final VaultConfig vaultConfig;
private final AppConfig appConfig;
public ProductService(VaultConfig vaultConfig,
@Autowired AppConfig appConfig) {
this.vaultConfig = vaultConfig;
this.appConfig = appConfig;
}
private void createAccessToken() {
String httpUrl = MessageFormat.format("{0}/api/v1/authentication/login",
appConfig.getProductServerUrl());
CloseableHttpClient client = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(httpUrl);
List<NameValuePair> httpParams = new ArrayList<NameValuePair>();
httpParams.add(new BasicNameValuePair("username", this.vaultConfig.getProductAdminUsername()));
httpParams.add(new BasicNameValuePair("password", this.vaultConfig.getProductAdminPassword()));
try {
httpPost.setEntity(new UrlEncodedFormEntity(httpParams));
CloseableHttpResponse closeableHttpResponse = client.execute(httpPost);
HttpEntity entity = closeableHttpResponse.getEntity();
String tokenDataJson = EntityUtils.toString(entity, "UTF-8");
String newAccessToken = new Gson().fromJson(tokenDataJson, Map.class).get("access_token").toString();
this.vaultConfig.setProductAccessToken(newAccessToken);
} catch (Exception e) {
logger.error("Unable to create access token: " + e.getMessage());
}
}
Test Attempt:
public class ProductServiceTest {
private ProductService productService;
@Mock
HttpClients httpClients;
@Mock
CloseableHttpClient closeableHttpClient;
@Mock
HttpPost httpPost;
@Mock
CloseableHttpResponse closeableHttpResponse;
@Mock
private VaultConfig vaultConfig;
@Mock
private AppConfig appConfig;
@BeforeEach
public void initialize() {
MockitoAnnotations.openMocks(this);
productService = new ProductService(vaultConfig, appConfig);
}
void getAccessTokenWhenEmpty() throws IOException {
//given
String expectedProductAccessToken = "ABC";
//and:
given(appConfig.getProductServerUrl()).willReturn("https://test.abcd.com");
given(closeableHttpClient.execute(httpPost)).willReturn(closeableHttpResponse);
given(vaultConfig.getProductAccessToken()).willReturn("");
//when
String actualProductAccessToken = ProductService.getAccessToken();
//then
Assertions.assertEquals(actualProductAccessToken,expectedProductAccessToken);
}
Error:
} catch (Exception e) {
java.net.UnknownHostException: test.abcd.com: unknown error
A easier, cleaner and less fragile approach is don't mock the HttpClient
. It just causes your test codes to have the pattern "mock return mock and the returned mock return anther mock which in turn return another mock which return ......" which looks very ugly and is a code smell to me.
Instead , use a real HTTPClient
instance and mock the external API using tools likes WireMock or MockWebServer
My favourite is MockWebServer which you can do something likes:
public class ProductServiceTest {
private ProductService productService;
@Mock
private AppConfig appConfig;
private MockWebServer server;
@Test
public void getAccessTokenWhenEmpty(){
server.start();
HttpUrl baseUrl = server.url("/api/v1/authentication/login");
given(appConfig.getProductServerUrl()).willReturn("http://" + baseUrl.host() +":" + baseUrl.port());
productService = new ProductService(vaultConfig, appConfig);
//stub the server to return the access token response
server.enqueue(new MockResponse().setBody("{\"access_token\":\"ABC\"}"));
//continues execute your test
}
}