无法Mock postInvoiceByRestService方法中的静态RestTemplate?求解决方案
问题分析
你无法Mock静态RestTemplate的核心原因有三点:
- 目标类中的
restTemplate是静态成员变量,且已直接初始化new RestTemplate(),你在测试中创建的mock实例并未替换掉类中的静态实例 @PrepareForTest注解未包含PeopleSoftInvoiceRestPublisher类,PowerMock需要对该类进行字节码修改才能操作其静态成员- 测试中仅局部创建了
RestTemplatemock对象,但未将其注入到目标类的静态字段中
正确Mock策略(PowerMock方案)
修改测试类,完成静态字段的替换和mock配置:
@RunWith(PowerMockRunner.class) // 必须把目标类PeopleSoftInvoiceRestPublisher加入PrepareForTest @PrepareForTest({ PeopleSoftInvoiceRestPublisher.class, DfiResources.class, DfiLog.class, UriComponentsBuilder.class }) @PowerMockIgnore("javax.swing.*") public class PeopleSoftInvoiceRestPublisherTest { @InjectMocks private PeopleSoftInvoiceRestPublisher peopleSoftInvoiceRestPublisher; @Mock private RestTemplate mockRestTemplate; @Before public void setup() { MockitoAnnotations.initMocks(this); // 替换目标类中的静态restTemplate字段为mock实例 Whitebox.setInternalState(PeopleSoftInvoiceRestPublisher.class, "restTemplate", mockRestTemplate); } @Test public void testCase() throws Exception { PeopleSoftRecordBuilder peopleSoftRecordBuilder = new PeopleSoftRecordBuilder(); // 静态mock DfiResources PowerMockito.mockStatic(DfiResources.class); when(DfiResources.getResource("PS_USER")).thenReturn("USER"); when(DfiResources.getResource("PS_PASSWORD")).thenReturn("PASSWORD"); when(DfiResources.getResource("PS_POST_URL")).thenReturn("https://example.com/hotels/42?filter={value}"); // mock DfiLog PowerMockito.mockStatic(DfiLog.class); // 构建测试数据 peopleSoftRecordBuilder.invoiceAmt(new BigDecimal(10.0)); peopleSoftRecordBuilder.invoiceNum("1234"); peopleSoftRecordBuilder.supplierNumber("567"); peopleSoftRecordBuilder.setQtyDelivered(new BigDecimal(210.0)); // mock UriComponentsBuilder的链式调用 PowerMockito.mockStatic(UriComponentsBuilder.class); UriComponentsBuilder mockBuilder = Mockito.mock(UriComponentsBuilder.class); when(UriComponentsBuilder.fromHttpUrl(Mockito.anyString())).thenReturn(mockBuilder); when(mockBuilder.toUriString()).thenReturn("https://example.com/hotels/42?filter={value}"); // 配置RestTemplate的mock行为 ResponseEntity<String> mockResponse = new ResponseEntity<>("mock response", HttpStatus.ACCEPTED); when(mockRestTemplate.exchange( Matchers.anyString(), Matchers.any(HttpMethod.class), Matchers.<HttpEntity<?>>any(), Matchers.<Class<String>>any() )).thenReturn(mockResponse); // 执行测试方法 peopleSoftInvoiceRestPublisher.postInvoiceByRestService(peopleSoftRecordBuilder); // 验证RestTemplate的exchange方法是否被调用 Mockito.verify(mockRestTemplate, Mockito.times(1)).exchange( Matchers.anyString(), Matchers.eq(HttpMethod.POST), Matchers.<HttpEntity<PostInvoiceRequestDTO>>any(), Matchers.eq(String.class) ); } }
关键修改点:
- 将
PeopleSoftInvoiceRestPublisher加入@PrepareForTest,让PowerMock可以修改其静态成员 - 使用
Whitebox.setInternalState()替换目标类的静态restTemplate字段为mock实例 - 正确mock静态类
UriComponentsBuilder的链式调用(原测试中只mock了类但没处理链式方法,会导致空指针) - 添加验证逻辑,确保mock的方法被正确调用
长期优化方案(重构代码)
静态依赖会导致测试困难,建议重构代码,将RestTemplate改为实例注入:
public class PeopleSoftInvoiceRestPublisher { private static final String UPDATED_BY = "DFIINVP"; private static final String TAX_ACCOUNT = "241900"; private static URL serverURL; // 改为实例成员,通过构造器注入 private final RestTemplate restTemplate; // 提供无参构造器兼容原有代码,同时提供带参数的构造器用于测试 public PeopleSoftInvoiceRestPublisher() { this.restTemplate = new RestTemplate(); } public PeopleSoftInvoiceRestPublisher(RestTemplate restTemplate) { this.restTemplate = restTemplate; } // ... 其他方法不变,postInvoiceByRestService中使用this.restTemplate调用 }
此时测试类无需使用PowerMock,直接用普通Mockito即可:
@RunWith(MockitoJUnitRunner.class) public class PeopleSoftInvoiceRestPublisherTest { @Mock private RestTemplate mockRestTemplate; @InjectMocks private PeopleSoftInvoiceRestPublisher peopleSoftInvoiceRestPublisher; @Test public void testCase() throws Exception { // ... 其他测试逻辑不变 // 配置RestTemplate mock行为 ResponseEntity<String> mockResponse = new ResponseEntity<>("mock response", HttpStatus.ACCEPTED); when(mockRestTemplate.exchange( anyString(), any(HttpMethod.class), any(HttpEntity.class), eq(String.class) )).thenReturn(mockResponse); peopleSoftInvoiceRestPublisher.postInvoiceByRestService(peopleSoftRecordBuilder); // 验证调用 verify(mockRestTemplate).exchange( anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(String.class) ); } }
这种方式更符合依赖注入原则,测试更简洁,也避免了PowerMock带来的字节码修改风险。
内容的提问来源于stack exchange,提问作者Ritesh
相关产品推荐
相关产品推荐

