如何用Mockito模拟第三方库?Java服务RequestClient模拟问题
问题原因
你当前的模拟代码无效,核心原因是:测试中mock的RequestClient实例,和getData()方法内部new出来的实例完全是两个独立对象,mock的配置不会作用到真实代码创建的对象上,所以仍然会触发真实的API调用。
解决方案
方案1:重构代码(推荐,符合Spring设计理念)
通过抽取RequestClient的创建逻辑为可注入的工厂类,让代码具备可测试性:
步骤1:定义RequestClient工厂接口与实现
// 工厂接口,统一管理RequestClient的创建 interface RequestClientFactory { <T> RequestClient<T> createRequestClient(); } // 生产环境的工厂实现 @Component class DefaultRequestClientFactory implements RequestClientFactory { @Override public <T> RequestClient<T> createRequestClient() { return new RequestClient<>(new HttpClientFactory()); } }
步骤2:修改CorrectionService,注入工厂
@Service class CorrectionService { private final RequestClientFactory requestClientFactory; // 构造注入(Spring官方推荐的依赖注入方式) public CorrectionService(RequestClientFactory requestClientFactory) { this.requestClientFactory = requestClientFactory; } BookingDetails getData() { // 通过工厂创建RequestClient,而非直接new RequestClient<BookingDetails> requestClient = requestClientFactory.createRequestClient(); ApiResponse<BookingDetails> apiResponse = requestClient.send(bookingRequest, BookingDetails.class); return apiResponse.getData(); } }
步骤3:编写测试代码
现在可以mock工厂类,让它返回预设的RequestClient mock实例:
@ExtendWith(MockitoExtension.class) // JUnit 5注解,JUnit 4用@RunWith(MockitoJUnitRunner.class) class CorrectionServiceTest { @InjectMocks CorrectionService correctionService; @Mock RequestClientFactory requestClientFactory; @Test void getData_shouldReturnMockedData() { // 1. 创建并配置RequestClient的mock实例 RequestClient<BookingDetails> mockRequestClient = mock(RequestClient.class); ApiResponse<BookingDetails> mockApiResponse = ApiResponse.<BookingDetails>builder().build(); when(mockRequestClient.send(any(), eq(BookingDetails.class))) .thenReturn(mockApiResponse); // 2. 让工厂返回mock的RequestClient when(requestClientFactory.createRequestClient()).thenReturn(mockRequestClient); // 3. 执行测试方法 BookingDetails result = correctionService.getData(); // 4. 验证逻辑(可选,确保send方法被正确调用) verify(mockRequestClient).send(any(), eq(BookingDetails.class)); } }
方案2:使用PowerMock(临时方案,不推荐)
如果暂时无法重构代码,可以用PowerMock拦截new操作,让它返回mock实例,但这种方式会让测试和代码实现强绑定,后续维护成本高:
测试代码示例
@RunWith(PowerMockRunner.class) // JUnit 4注解,JUnit 5需用PowerMockExtension @PrepareForTest({CorrectionService.class, RequestClient.class}) class CorrectionServiceTest { @InjectMocks CorrectionService correctionService; @Test void getData_shouldReturnMockedData() throws Exception { // 1. 创建并配置RequestClient的mock实例 RequestClient<BookingDetails> mockRequestClient = mock(RequestClient.class); ApiResponse<BookingDetails> mockApiResponse = ApiResponse.<BookingDetails>builder().build(); when(mockRequestClient.send(any(), eq(BookingDetails.class))) .thenReturn(mockApiResponse); // 2. 拦截RequestClient的new操作,返回mock实例 PowerMockito.whenNew(RequestClient.class) .withArgumentMatchers(any(HttpClientFactory.class)) .thenReturn(mockRequestClient); // 3. 执行测试 BookingDetails result = correctionService.getData(); // 4. 验证调用 verify(mockRequestClient).send(any(), eq(BookingDetails.class)); } }
注意:使用PowerMock需要额外引入
powermock-module-junit4和powermock-api-mockito2等依赖,且该方式会绕过Java的正常类加载机制,可能引发意外问题。
内容的提问来源于stack exchange,提问作者Rahul Raj
相关产品推荐
相关产品推荐

