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

遵循Java组合(Composition)模式时如何在Mockito中mock依赖类?

概念澄清:组合与依赖注入的差异

你之前的认知存在一个常见误区:组合和依赖注入并不是互斥的设计概念,二者属于完全不同的维度:

  • 组合是面向对象中类关系的类型定义,代表强绑定的Has-A(拥有)关系,核心判定规则是从属对象的生命周期和宿主完全绑定:宿主实例销毁时,从属对象也会同步销毁,没有其他外部引用持有该从属对象。它只定义关系的性质,并不限制从属对象的实例化方式。
  • 依赖注入是实现依赖管理的编码技巧,核心逻辑是不在类内部硬编码依赖的实例化逻辑,而是通过构造函数、Setter等方式从外部传入依赖实例。它是实现依赖的手段,完全可以和组合搭配使用。

你完全可以通过构造函数传入Foo实例,同时保证Bar和Foo是组合关系:只要Bar不对外暴露Foo的引用,外部也不会单独持有传入的Foo实例,Foo的生命周期就完全和Bar绑定,完全符合组合的定义。

Mockito测试解决方案

这里提供两种方案,优先推荐第一种重构方案,对代码侵入性极低,也不会破坏原有封装和组合特性。

方案1:重构Bar类新增包级私有构造(推荐)

给Bar类新增一个仅对同包下测试类可见的构造函数,用于传入mock的Foo实例,对外仍然保留原有的无参构造,不影响原有业务代码的使用:

public class Bar implements MyRequiredObjectsInterface {
  private Foo foo;
 
  // 对外公开的无参构造,兼容原有使用逻辑,内部自行实例化Foo
  public Bar() {
    this(new Foo());
  }

  // 包级私有构造,仅同包下的测试类可调用,用于传入mock的Foo实例
  Bar(Foo foo) {
    this.foo = foo;
  }

  @Override
  public String stringOne() {
    return foo.stringOne();
  }

  @Override
  public List<String> listOne() {
    return foo.listOne();
  }
}

对应的测试代码如下:

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;

public class BarTest {
  private Foo foo;
  private Bar bar;

  @Before
  public void setUp() {
    // mock Foo实例
    this.foo = Mockito.mock(Foo.class);
    // 传入mock实例构造Bar
    this.bar = new Bar(foo);
  }

  @Test
  public void testListOne() {
    // 预设mock的返回值
    List<String> mockResult = Arrays.asList("a", "b", "c");
    Mockito.when(foo.listOne()).thenReturn(mockResult);
    // 验证Bar的方法调用结果符合预期
    assertEquals(mockResult, bar.listOne());
  }
}

方案2:无需修改生产代码,反射替换私有字段

如果不能修改原有Bar类的代码,可以通过反射强制修改Bar内部的私有foo字段为mock对象:

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;

public class BarTest {
  private Foo foo;
  private Bar bar;

  @Before
  public void setUp() throws NoSuchFieldException, IllegalAccessException {
    this.bar = new Bar();
    this.foo = Mockito.mock(Foo.class);
    // 反射获取Bar类的foo字段
    Field fooField = Bar.class.getDeclaredField("foo");
    // 开放私有字段的访问权限
    fooField.setAccessible(true);
    // 替换Bar内部的foo实例为mock对象
    fooField.set(bar, foo);
  }

  @Test
  public void testListOne() {
    List<String> mockResult = Arrays.asList("a", "b", "c");
    Mockito.when(foo.listOne()).thenReturn(mockResult);
    assertEquals(mockResult, bar.listOne());
  }
}

该方案的缺点是测试代码和Bar类的内部字段强耦合,一旦Foo字段名修改,测试就会失效,仅作为临时方案使用。

内容的提问来源于stack exchange,提问作者TechGuyChris

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 17:36:00