You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Mockito模拟Apache Post HTTPClient时触发UnknownHostException报错

问题原因

你的Mock完全没有生效,实际执行时走的是真实的HTTP请求,所以触发了未知域名报错,核心问题有3个:

  • 业务代码里CloseableHttpClient client = HttpClients.createDefault()是在方法内部直接新建的真实实例,你测试类里声明的@Mock CloseableHttpClient closeableHttpClient从来没有被传入到业务类中,和实际执行方法用到的client完全是两个对象。
  • 你在测试中写的stub given(closeableHttpClient.execute(httpPost)).willReturn(closeableHttpResponse)里的httpPost也是你自己Mock的实例,和业务方法内部new HttpPost(httpUrl)生成的真实对象不是同一个,参数匹配不上,就算client被注入了这个stub也不会触发。
  • 测试方法漏加@Test注解,且你调用ProductService.getAccessToken()是静态类调用,和你初始化的productService实例没有关联。
修复方案

优先选重构业务代码的方案,不要在方法内部硬编码初始化依赖,否则单元测试很难隔离外部依赖。

方案1:重构依赖注入(推荐)

把HttpClient从方法内部的局部变量改成类的成员变量,通过构造器传入,生产环境传入HttpClients.createDefault()的真实实例,测试时传入Mock实例即可。
修改后的业务代码:

public class ProductService {

    private final VaultConfig vaultConfig;
    private final AppConfig appConfig;
    private final CloseableHttpClient httpClient;

    // 构造器注入HttpClient,解耦初始化逻辑
    public ProductService(VaultConfig vaultConfig,
                          AppConfig appConfig,
                          CloseableHttpClient httpClient) {
            this.vaultConfig = vaultConfig;
            this.appConfig = appConfig;
            this.httpClient = httpClient;
    }

    private void createAccessToken() {
        String httpUrl = MessageFormat.format("{0}/api/v1/authentication/login",
            appConfig.getProductServerUrl());
        // 删除方法内部直接创建HttpClient的逻辑
        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 = httpClient.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());
        }
    }
}

对应修改后的测试代码:

public class ProductServiceTest {

    private ProductService productService;
    @Mock
    CloseableHttpClient closeableHttpClient;
    @Mock
    CloseableHttpResponse closeableHttpResponse;
    @Mock
    private VaultConfig vaultConfig;
    @Mock
    private AppConfig appConfig;
    @Mock
    private HttpEntity httpEntity;

    @BeforeEach
    public void initialize() throws IOException {
        MockitoAnnotations.openMocks(this);
        // 注入Mock的HttpClient到业务实例
        productService = new ProductService(vaultConfig, appConfig, closeableHttpClient);

        // 配置通用Stub,用any()匹配方法内部生成的真实对象,不要用自己Mock的HttpPost实例匹配
        given(appConfig.getProductServerUrl()).willReturn("https://test.abcd.com");
        given(closeableHttpClient.execute(any(HttpPost.class))).willReturn(closeableHttpResponse);
        given(closeableHttpResponse.getEntity()).willReturn(httpEntity);
        given(EntityUtils.toString(any(HttpEntity.class), eq("UTF-8"))).willReturn("{\"access_token\":\"ABC\"}");
    }

    @Test
    void getAccessTokenWhenEmpty() throws IOException {
        //given
        String expectedProductAccessToken = "ABC";
        given(vaultConfig.getProductAccessToken()).willReturn("");

        //when
        // 用初始化好的业务实例调用方法,不要静态调用
        String actualProductAccessToken = productService.getAccessToken();

        //then
        Assertions.assertEquals(expectedProductAccessToken, actualProductAccessToken);
    }
}

方案2:用PowerMock mock内部构造逻辑(不推荐)

如果你完全不想修改业务代码,需要引入PowerMock对HttpClients.createDefault()静态方法、new HttpPost()构造方法做Mock,让内部生成的对象替换成你准备的Mock实例。这种方式测试代码和业务实现耦合度极高,后续业务代码小改动就可能导致测试失效,非必要不使用。

核心原则:用Mockito做单元测试时,所有需要Mock的依赖都不能在被测方法内部直接new初始化,必须通过注入的方式传入,否则Mockito无法代理到真实执行的对象。

内容的提问来源于stack exchange,提问作者mattsmith5

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 05:39:13