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

Junit 5中如何Mock java.net.HttpClient的send方法?

问题原因
  • 被测试类的HttpClient实例是构造方法内部硬编码创建,没有对外开放注入入口,默认无法将mock对象替换内部的真实HttpClient实例。
  • 测试类中mock的HttpClient未注入到被测试实例:你在setUp中手动mock的httpClient是独立对象,既未加@Mock注解让Mockito管理,也未手动赋值给被测试类的httpClient属性,被测试类运行时始终使用构造方法新建的真实实例,因此mock不生效。
  • 校验逻辑不匹配:你verify时传入的request是测试类单独mock的对象,而业务代码中send方法传入的是方法内部动态构建的HttpRequest实例,两个对象完全不同,就算mock生效校验也会失败。
  • 额外注意:java.net.http.HttpClient是final类,低版本Mockito默认不支持mock final类,需要额外配置才能正常mock。
解决方案

方案1(推荐):调整业务代码适配依赖注入原则

修改业务代码的构造逻辑,将HttpClient改为注入方式,不要在构造内部硬编码创建,既符合Spring依赖注入规范,也方便单元测试。

修改后业务代码

public class EsignEventProducer {
    private String producerEndpoint;
    private HttpClient httpClient;
    
    // 新增HttpClient作为构造入参,由Spring注入实例
    @Autowired
    public EsignEventProducer(@Value("${producer.http.endpoint}") String producerEndpoint, HttpClient httpClient) {
        this.producerEndpoint = producerEndpoint;
        this.httpClient = httpClient;
    }

    // 原有sendMessage逻辑保持不变
    public void sendMessage(ObjectMapper objectMapper, String jsonString, String xCorrelationId) throws Exception {
        ProduceRequest produceRequest=new ProduceRequest();
        ProduceRecord produceRecord=new ProduceRecord();
        produceRecord.setValue(Optional.of(objectMapper.readTree(jsonString)));
        produceRequest.setRecords(Arrays.asList(produceRecord));
        
        HttpRequest request=HttpRequest.newBuilder()
                .uri(URI.create(producerEndpoint))
                .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(produceRequest)))
                .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON.toString())
                .setHeader(IdTypeConstants.XCORRELATIONID, xCorrelationId)
                .build();
        
        httpClient.send(request, HttpResponse.BodyHandlers.ofString());
    }
}

新增Spring配置类提供HttpClient的全局实例:

@Configuration
public class HttpClientConfig {
    @Bean
    public HttpClient httpClient() {
        return HttpClient.newBuilder().build();
    }
}

修改后测试代码

// 注意要加Mockito的测试注解,适配Junit5
@ExtendWith(MockitoExtension.class)
class EsignEventProducerTest {
    
    @InjectMocks
    private EsignEventProducer esignEventProducer;
    
    // 给HttpClient加@Mock注解,让Mockito管理mock实例,自动注入到被测试类
    @Mock
    private HttpClient httpClient;
    @Mock
    private HttpResponse<String> httpResponse;
    
    private MyEvent schema;

    @BeforeEach
    void setUp() {
        schema = new MyEvent();
        schema.setId("123");
        schema.setName("PACKAGE_COMPLETE");
        schema.setCreatedDate("2021-10-20T16:17:58.408Z");
        schema.setSessionUser("dummySession");
    }
    
    @Test
    void testSendMessage() throws Exception {
        ReflectionTestUtils.setField(esignEventProducer, "producerEndpoint", "http://localhost:8080/endpointservice/my_topic");
        ObjectMapper objectMapper = new ObjectMapper();
        String jsonString = objectMapper.writeValueAsString(schema);
        String xCorrelationId = "dummyXCorrelationId";
        
        // 用any匹配所有入参,避免实例不匹配的问题
        Mockito.when(httpClient.send(Mockito.any(HttpRequest.class), Mockito.any(HttpResponse.BodyHandler.class))).thenReturn(httpResponse);
        
        esignEventProducer.sendMessage(objectMapper, jsonString, xCorrelationId);
        
        // 校验也用any匹配,不要用测试类单独mock的request对象
        Mockito.verify(httpClient).send(Mockito.any(HttpRequest.class), Mockito.any(HttpResponse.BodyHandler.class));
    }
}

方案2:不修改业务代码,手动注入mock实例

如果不想调整业务代码,可以通过反射直接把你mock的HttpClient赋值给被测试类的对应属性,在测试方法执行前加一行代码即可:

ReflectionTestUtils.setField(esignEventProducer, "httpClient", httpClient);

Mock final类适配

如果运行时报错提示无法mock final类,需要添加Mockito inline依赖支持final类mock:
Gradle配置:

testImplementation 'org.mockito:mockito-inline:4.11.0' // 版本和你现有Mockito版本保持一致即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 06:06:03