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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 09:15:35