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
相关产品推荐
相关产品推荐

