如何在不传入模拟对象的情况下利用Mockito模拟类内部创建的对象行为

作者:袖梨 2026-06-17

本文介绍一种符合 mockito 原则、无需 powermockito 或反射的轻量级方案:通过受保护的构造函数注入依赖,实现对类内部新建对象(如 client)的行为模拟,兼顾可测试性与代码整洁性。

本文介绍一种符合 mockito 原则、无需 powermockito 或反射的轻量级方案:通过受保护的构造函数注入依赖,实现对类内部新建对象(如 client)的行为模拟,兼顾可测试性与代码整洁性。

在单元测试中,当被测类(如 MyClass)自行实例化其依赖(例如通过 setupClient() 创建 Client 对象),而该依赖并未通过构造函数或 setter 注入时,直接用 Mockito 模拟其行为会受限——因为 Mockito 无法拦截私有方法调用或控制内部对象生命周期。但强行使用 PowerMockito 或修改类结构(如添加 @PrepareForTest)不仅增加复杂度,还违背“测试友好设计”的初衷。

推荐实践:引入受保护的构造函数(Protected Constructor)

这不是破坏封装,而是为测试提供一条合法、低侵入的扩展路径。核心思想是:保留原有无参构造函数供生产环境使用,同时新增一个带依赖参数的 protected 构造函数,仅用于测试场景:

public class MyClass {    private final Client someClient;    // 生产环境使用的默认构造函数    public MyClass() {        this.someClient = setupClient();    }    // 仅用于测试的受保护构造函数(不暴露给外部模块)    protected MyClass(Client client) {        this.someClient = Objects.requireNonNull(client, "Client must not be null");    }    private Client setupClient() {        // 实际初始化逻辑,如 new OkHttpClient() 或配置连接池等        return new DefaultClient();    }    public void methodIWantToTest(String requestString) {        this.someClient.makeApiCall(requestString);        // 其他业务逻辑    }}

在测试类中,直接使用该受保护构造函数传入 Mock 对象:

public class MyClassTest {    private MyClass SUT;    private Client mockClient;    @BeforeEach    void setUp() {        mockClient = Mockito.mock(Client.class);        SUT = new MyClass(mockClient); // ✅ 使用受保护构造函数    }    @Test    void testMethodIWantToTest() {        // 配置模拟行为        Mockito.when(mockClient.makeApiCall(Mockito.anyString()))               .thenReturn(Response.success("mocked response"));        // 执行被测方法        SUT.methodIWantToTest("GET /api/users");        // 验证交互        Mockito.verify(mockClient).makeApiCall("GET /api/users");        Mockito.verifyNoMoreInteractions(mockClient);    }}

优势说明

  • 完全兼容标准 Mockito,无需额外依赖(如 PowerMockito);
  • 不违反面向对象原则,protected 修饰符确保测试专用入口不被误用;
  • 保持类职责清晰:构造逻辑仍由类自身管理,仅开放可控的测试钩子;
  • 易于演进:未来若采用 Spring 等 DI 框架,可自然升级为 @Autowired 构造注入。

⚠️ 注意事项

  • 避免将 protected 构造函数声明为 public,防止被非测试代码滥用;
  • 若 Client 是 final 类或方法不可 mock(如 JDK 内置类),需配合 mockito-inline(Mockito 3.4.0+)启用字节码增强;
  • setupClient() 中若含副作用(如网络/IO 初始化),务必确保其在测试中被完全绕过——这正是本方案的价值所在。

总结:可测试性不是测试框架的义务,而是设计的责任。通过微小的构造函数重构,即可在零反射、零第三方工具的前提下,让私有依赖变得“可替换、可验证、可预测”,这才是 Mockito 倡导的正向测试实践。

相关文章

精彩推荐