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

如何Mock构造函数中通过外部方法初始化的对象?

你的问题根源在于HttpClient字段是通过硬编码调用单例ApacheHttpSingleton.getHttpClient()初始化的,而不是通过依赖注入传入的。Mockito的@InjectMocks只会处理那些通过构造函数、Setter方法或者字段注入的依赖,对于这种硬编码获取的对象,它没法自动替换成Mock实例。

下面给你几种可行的解决方案,优先推荐最符合依赖注入原则的方案:

方案1:重构原类,让HttpClient通过构造函数注入(推荐)

这是最干净的做法,既遵循了依赖注入的设计原则,也让测试变得简单。修改你的SampleConstructor类,把HttpClient也作为构造函数参数传入:

final HttpClient httpClient;
final HttpUtils httpUtils;

@Autowired
public SampleConstructor(HttpUtils httpUtils, HttpClient httpClient) {
    this.httpUtils = httpUtils;
    this.httpClient = httpClient;
}

然后在Spring配置类中定义HttpClient的Bean,让它返回单例实例:

@Configuration
public class HttpClientConfig {
    @Bean
    public HttpClient httpClient() {
        return ApacheHttpSingleton.getHttpClient();
    }
}

修改完成后,你的测试代码就可以正常使用@Mock和@InjectMocks了:

@ExtendWith(MockitoExtension.class)
public class SampleConstructorTest {
    @Mock
    HttpUtils mockHttpUtils;
    @Mock
    HttpClient mockHttpClient;
    @InjectMocks
    SampleConstructor mockService;

    // 测试方法...
}

方案2:不修改原类,通过反射替换final字段

如果因为某些限制不能修改原类的构造函数,你可以用反射来强制替换final的httpClient字段。Mockito提供了Whitebox工具类(属于内部API),或者你也可以用Java原生反射实现:

用Mockito Whitebox实现

@ExtendWith(MockitoExtension.class)
public class SampleConstructorTest {
    @Mock
    HttpUtils mockHttpUtils;
    @Mock
    HttpClient mockHttpClient;
    SampleConstructor mockService;

    @BeforeEach
    void setUp() {
        mockService = new SampleConstructor(mockHttpUtils);
        // 替换final字段
        Whitebox.setInternalState(mockService, "httpClient", mockHttpClient);
    }
}

用Java原生反射实现(不依赖Mockito内部类)

@ExtendWith(MockitoExtension.class)
public class SampleConstructorTest {
    @Mock
    HttpUtils mockHttpUtils;
    @Mock
    HttpClient mockHttpClient;
    SampleConstructor mockService;

    @BeforeEach
    void setUp() throws NoSuchFieldException, IllegalAccessException {
        mockService = new SampleConstructor(mockHttpUtils);
        
        Field httpClientField = SampleConstructor.class.getDeclaredField("httpClient");
        httpClientField.setAccessible(true);
        
        // 关闭final字段的访问限制
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(httpClientField, httpClientField.getModifiers() & ~Modifier.FINAL);
        
        httpClientField.set(mockService, mockHttpClient);
    }
}

方案3:用PowerMock Mock单例静态方法

另一种思路是MockApacheHttpSingleton.getHttpClient()这个静态方法,让它返回你的Mock对象。这种方法不需要修改原类,但需要引入PowerMock依赖:

@RunWith(PowerMockRunner.class)
@PrepareForTest(ApacheHttpSingleton.class)
public class SampleConstructorTest {
    @Mock
    HttpUtils mockHttpUtils;
    @Mock
    HttpClient mockHttpClient;
    SampleConstructor mockService;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
        // Mock静态方法
        PowerMockito.mockStatic(ApacheHttpSingleton.class);
        when(ApacheHttpSingleton.getHttpClient()).thenReturn(mockHttpClient);
        
        mockService = new SampleConstructor(mockHttpUtils);
    }
}

这种方法会增加测试的复杂度,一般作为无法修改原类时的备选方案。

内容的提问来源于stack exchange,提问作者Mayank Aggarwal

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 08:27:52