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

无法Mock postInvoiceByRestService方法中的静态RestTemplate?求解决方案

问题分析

你无法Mock静态RestTemplate的核心原因有三点:

  • 目标类中的restTemplate是静态成员变量,且已直接初始化new RestTemplate(),你在测试中创建的mock实例并未替换掉类中的静态实例
  • @PrepareForTest注解未包含PeopleSoftInvoiceRestPublisher类,PowerMock需要对该类进行字节码修改才能操作其静态成员
  • 测试中仅局部创建了RestTemplate mock对象,但未将其注入到目标类的静态字段中
正确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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 19:07:02