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

