javaspring-bootmockitoapache-httpcomponents

Mocking Apache Post HTTPClient using Mockito


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

Solution

  • 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
    
        }
    
    
    }