如何用Mockito模拟方法?被测方法内部实例的Mock问题
解决方法
原因分析
你没法Mock MyHttpClient 的核心问题是:MyDataSourceImpl 的 execute 方法里直接通过 new MyHttpClient(...) 创建了实例,Mockito无法拦截这种硬编码的对象实例化,测试类里@Mock的MyHttpClient和代码里实际运行的完全是两个对象,所以Mock规则不会生效。
方案一:通过依赖注入重构代码(推荐)
遵循依赖反转原则,把MyHttpClient的创建权交给外部,通过构造方法注入工厂类,这样测试时可以直接传入Mock实例。
1. 修改MyDataSourceImpl类
public class MyDataSourceImpl implements MyDataSource { private final PropertieService propertieService; private final MyHttpClientFactory httpClientFactory; // 构造方法注入依赖 @Autowired public MyDataSourceImpl(PropertieService propertieService, MyHttpClientFactory httpClientFactory) { this.propertieService = propertieService; this.httpClientFactory = httpClientFactory; } public HttpResponse execute(String url){ MyHttpClient myClient = httpClientFactory.create(url, propertieService.getProperties()); HttpResponse response = myClient.performCall(); this.someOtherStuff(response); return response; } } // 新增HttpClient工厂接口 public interface MyHttpClientFactory { MyHttpClient create(String url, Map<String, String> properties); } // 生产环境的工厂实现 public class DefaultMyHttpClientFactory implements MyHttpClientFactory { @Override public MyHttpClient create(String url, Map<String, String> properties) { return new MyHttpClient(url, properties); } }
2. 修改测试类
@RunWith(MockitoJUnitRunner.class) public class MyDataSourceImplTest { @InjectMocks private MyDataSourceImpl myDataSourceImpl; @Mock private PropertieService propertieService; @Mock private MyHttpClientFactory httpClientFactory; @Mock private MyHttpClient myHttpClient; @Test public void simpleCall(){ // Mock配置参数返回 when(propertieService.getProperties()).thenReturn(new HashMap<String,String>()); // Mock工厂返回Mock的HttpClient when(httpClientFactory.create(anyString(), anyMap())).thenReturn(myHttpClient); // Mock请求返回结果 HttpResponse mockResponse = new HttpResponse(); // 替换为你需要的模拟响应 when(myHttpClient.performCall()).thenReturn(mockResponse); HttpResponse response = myDataSourceImpl.execute("https://nothing.com"); assertEquals(response, mockResponse); // 验证方法调用次数 verify(myHttpClient, times(1)).performCall(); } }
方案二:通过Spy重构代码(最小改动)
如果不想引入工厂类,可将创建MyHttpClient的逻辑抽成protected方法,再用Mockito Spy来Mock这个方法,返回预设的Mock实例。
1. 修改MyDataSourceImpl类
public class MyDataSourceImpl implements MyDataSource { @Autowired PropertieService propertieService; public HttpResponse execute(String url){ MyHttpClient myClient = createHttpClient(url, propertieService.getProperties()); HttpResponse response = myClient.performCall(); this.someOtherStuff(response); return response; } // 抽离创建逻辑为protected方法,方便测试Mock protected MyHttpClient createHttpClient(String url, Map<String, String> properties) { return new MyHttpClient(url, properties); } }
2. 修改测试类
@RunWith(MockitoJUnitRunner.class) public class MyDataSourceImplTest { // 使用Spy而非@InjectMocks,用于Mock类内部方法 @Spy private MyDataSourceImpl myDataSourceImpl; @Mock private PropertieService propertieService; @Mock private MyHttpClient myHttpClient; @Test public void simpleCall(){ // 手动注入Mock的配置服务 Whitebox.setInternalState(myDataSourceImpl, "propertieService", propertieService); // Mock配置参数返回 when(propertieService.getProperties()).thenReturn(new HashMap<String,String>()); // MockHttpClient创建方法返回预设实例 doReturn(myHttpClient).when(myDataSourceImpl).createHttpClient(anyString(), anyMap()); // Mock请求返回结果 HttpResponse mockResponse = new HttpResponse(); when(myHttpClient.performCall()).thenReturn(mockResponse); HttpResponse response = myDataSourceImpl.execute("https://nothing.com"); assertEquals(response, mockResponse); verify(myHttpClient, times(1)).performCall(); } }
注:Whitebox是Mockito内置的反射工具,若使用Spring测试环境,也可替换为ReflectionTestUtils。
方案三:使用PowerMock(不推荐)
如果完全不想修改业务代码,可通过PowerMock拦截new操作,但该工具会破坏Mockito原有机制,增加测试复杂度,仅作为临时方案使用。
测试类示例
@RunWith(PowerMockRunner.class) @PrepareForTest(MyDataSourceImpl.class) // 声明需要拦截new操作的类 public class MyDataSourceImplTest { @InjectMocks private MyDataSourceImpl myDataSourceImpl; @Mock private PropertieService propertieService; @Mock private MyHttpClient myHttpClient; @Test public void simpleCall() throws Exception { when(propertieService.getProperties()).thenReturn(new HashMap<String,String>()); // 拦截new MyHttpClient操作,返回Mock实例 whenNew(MyHttpClient.class).withAnyArguments().thenReturn(myHttpClient); // Mock请求返回结果 HttpResponse mockResponse = new HttpResponse(); when(myHttpClient.performCall()).thenReturn(mockResponse); HttpResponse response = myDataSourceImpl.execute("https://nothing.com"); assertEquals(response, mockResponse); } }
内容的提问来源于stack exchange,提问作者niconosave
相关产品推荐
相关产品推荐

