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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 05:07:39