Mockito实战:如何Mock方法内部实例化的对象?
这个问题很典型——当代码直接在方法内部创建依赖对象时,单元测试就很难Mock掉这些外部依赖,导致测试时会真实调用第三方服务,出现连接拒绝的错误。下面给你一步步解决的方案:
第一步:重构业务代码,让依赖可替换
要Mock掉AppointmentClient,首先得让这个依赖能被外部替换,而不是在方法内部硬编码创建。这里有两种常用方案:
方案1:构造函数注入依赖(推荐)
把AppointmentClient的创建逻辑移到类外部,通过构造函数注入进来,生产环境传真实的客户端实例,测试时传Mock对象即可:
public class AppointmentService { private final AppointmentClient appointmentClient; // 构造函数注入客户端依赖 public AppointmentService(AppointmentClient appointmentClient) { this.appointmentClient = appointmentClient; } public Appointment addAppointment(String client, Appointment appointment) { Appointment outputAppointment = null; if (appointment.getId() == null) { outputAppointment = appointmentClient.addAppointment(client, appointment); } return outputAppointment; } }
方案2:抽取依赖获取逻辑为protected方法(兼容老代码)
如果因为项目限制不能修改构造函数,可以把获取AppointmentClient的逻辑抽成一个protected方法,方便测试时通过子类或Mockito Spy覆盖:
public class AppointmentService { public Appointment addAppointment(String client, Appointment appointment) { Appointment outputAppointment = null; if (appointment.getId() == null) { AppointmentClient appointmentClient = getAppointmentClient(); outputAppointment = appointmentClient.addAppointment(client, appointment); } return outputAppointment; } // 抽取成protected方法,测试时可覆盖 protected AppointmentClient getAppointmentClient() { String esbUrl = new ESBUrlHelper().getEsbUrl(); return AppointmentClientFactory.getUnsecuredClient(esbUrl); } }
第二步:编写Mock后的单元测试
根据上面的重构方案,对应编写单元测试代码:
针对方案1的测试代码
用Mockito的@Mock创建客户端的Mock实例,@InjectMocks自动把Mock注入到AppointmentService中:
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) public class AppointmentServiceTest { private static final String CLIENT_STRING = "test-client"; @Mock private AppointmentClient appointmentClient; // 创建Mock的第三方客户端 @InjectMocks private AppointmentService appointmentService; // 自动将Mock注入服务类 @Test public void shouldAddAppointment() { // 准备测试数据 Appointment inputAppointment = new Appointment(); inputAppointment.setId(null); // 确保进入新增分支 Appointment expectedAppointment = new Appointment(); expectedAppointment.setId(123L); // 模拟第三方返回的预约对象 // 配置Mock行为:调用addAppointment时返回预期对象 when(appointmentClient.addAppointment(any(String.class), any(Appointment.class))) .thenReturn(expectedAppointment); // 执行测试方法 Appointment result = appointmentService.addAppointment(CLIENT_STRING, inputAppointment); // 断言结果 assertNotNull(result); // 可选:验证客户端方法确实被调用了一次 // verify(appointmentClient, times(1)).addAppointment(CLIENT_STRING, inputAppointment); } }
针对方案2的测试代码
用Mockito Spy覆盖getAppointmentClient方法,让它返回Mock的客户端:
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) public class AppointmentServiceTest { private static final String CLIENT_STRING = "test-client"; @Spy private AppointmentService appointmentService = new AppointmentService(); @Mock private AppointmentClient appointmentClient; @Test public void shouldAddAppointment() { // 准备测试数据 Appointment inputAppointment = new Appointment(); inputAppointment.setId(null); Appointment expectedAppointment = new Appointment(); expectedAppointment.setId(123L); // 让Spy的getAppointmentClient方法返回Mock客户端 doReturn(appointmentClient).when(appointmentService).getAppointmentClient(); // 配置Mock客户端的行为 when(appointmentClient.addAppointment(any(String.class), any(Appointment.class))) .thenReturn(expectedAppointment); // 执行测试 Appointment result = appointmentService.addAppointment(CLIENT_STRING, inputAppointment); // 断言 assertNotNull(result); } }
为什么原来的测试会失败?
你原来的代码在addAppointment方法内部直接创建AppointmentClient实例,单元测试时无法替换这个真实的客户端,导致测试代码会尝试连接第三方REST服务——而测试环境中这个服务通常是不可用的,所以出现了Connection refused的错误。
通过重构让依赖可注入,再用Mockito Mock掉外部依赖,就能让单元测试脱离真实服务独立运行啦。
内容的提问来源于stack exchange,提问作者java12399900
相关产品推荐
相关产品推荐

