Skip to content

Mockito

本文介绍Mockito框架。

1. 测试替身

在软件开发中,**测试替身(Test Double)**是一个通用的术语,用来指代那些在测试中为了替换真实对象而创建的“伪造”对象。

就像电影拍摄中使用替身演员来完成危险动作或节省成本一样,程序测试中使用“替身”来隔离被测代码,解决依赖项难以操作、速度慢或不可控的问题。

为什么要用测试替身?

  • 隔离外部依赖:比如数据库、第三方 API 或文件系统。
  • 提高测试速度:真实的数据库操作很慢,替身数据库在内存中运行,速度极快。
  • 模拟极端情况:比如模拟网络超时、服务器 500 错误等真实环境难以复现的场景。
  • 确定性:真实环境(如获取当前时间)是变化的,替身可以返回固定的值。

测试替身可以分为以下五种:

  • Dummy(哑物):最简单的替身。它只是为了填充参数列表而存在,永远不会被真正调用或使用
    • 场景:某个方法需要传 3 个参数,但测试的功能只涉及前 2 个,因此,第三个参数使用Dummy测试替身填充。
  • Stub(桩):为测试提供固定的答案。它实现了接口,但内部逻辑是死代码,只返回预定义好的数据
    • 场景:模拟获取天气,无论真实天气如何,Stub 永远返回“晴天”。
  • Spy(间谍):它会记录自己被调用的情况。它不仅能返回数据,还会偷偷记下谁调用了它、传了什么参数、调用了几次。
    • 场景:测试发送邮件功能,通过 Spy 确认 send() 方法确实被触发了一次。
  • Mock (模拟对象):这是最容易被误用的词。Mock的核心在于行为验证。假如预先设定:“期望这个对象被调用 A 方法,参数是 B,返回 C”。如果测试结束时这些期望没达成,测试就失败。
    • 场景:验证转账功能中,银行接口必须且只能被调用一次。
  • Fake (伪对象):Fake拥有简化版的业务逻辑,它确实能工作,但因为性能或环境限制,不适合用于生产环境。
    • 场景:内存数据库(如 H2)代替真实的 MySQL;用一个简单的 Map 代替复杂的Redis 缓存。

Mockito框架就是用于构造测试替身的。

2. Mockito

2.1 基本使用

首先引入依赖:

xml
<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-core</artifactId>
  <version>5.22.0</version>
</dependency>

然后,在测试代码中就可以按照以下步骤使用Mockito了:

  1. 创建测试替身:也就是对于真实对象,创建出一个替身对象来,之后的操作都是在替身对象上进行;
  2. 打桩:定义当调用替身对象的某个方法时,该方法应该执行什么逻辑、返回什么值;
  3. 执行:在替身对象上执行某个操作,即调用某个方法(一般都是打什么桩就调用什么方法);
  4. 验证:验证在替身对象上调用的方法参数、次数等;

案例:假设现在有一个功能,当用户点击“立即下单”时,业务系统调用第三方快递公司(如顺丰、菜鸟)的 API 来下快递单,并获取单号。但是,在测试中,我们不想真的去调用快递公司的接口(那会产生真实订单和费用),所以可以用 Mockito 来“骗”过业务逻辑。

首先项目代码中真实存在的快递下单服务:

java
public interface ExpressApi {
    // 调用第三方接口获取运单号
    String createOrder(String address, String phone);
}
java
public class OrderManager {
    private final ExpressApi expressApi;

    public OrderManager(ExpressApi expressApi) {
        this.expressApi = expressApi;
    }

  	/**
  	* 下单
  	*/
    public String placeOrder(String address, String phone) {
        // 业务逻辑:校验手机号
        if (phone == null || phone.length() < 11) {
            return "INVALID_PHONE";
        }
        
        // 调用外部快递接口
        String trackingNumber = expressApi.createOrder(address, phone);
        
        return "ORDER_CREATED: " + trackingNumber;
    }
}

然后,在测试代码中我们就可以使用Mockito模拟快递下单动作:

java
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;

@Test
void testPlaceOrder_Success() {
    // 1. 创建替身 (Mock)
    ExpressApi mockExpress = mock(ExpressApi.class);
    OrderManager manager = new OrderManager(mockExpress);

    // 2. 打桩 (Stubbing)
    // 预设:只要调用接口,就返回假单号 "SF12345678"
    when(mockExpress.createOrder(anyString(), anyString()))
        .thenReturn("SF12345678");

    // 执行被测业务
    String result = manager.placeOrder("北京市朝阳区", "13800138000");

    // 3. 验证 (Verify)
    // 验证返回结果是否包含 Mock 的单号,JUnit提供的功能
    assertEquals("ORDER_CREATED: SF12345678", result);

    // 验证业务系统是否真的去调了快递接口(Mockito提供的功能)
    verify(mockExpress).createOrder("北京市朝阳区", "13800138000");
}

2.2 创建替身

在Mockito中,创建替身对象的方法主要在Mockito类中,主要分为两类:

  • mockmock() 创造的是一个完全的空壳;
  • spyspy() 创造的是一个真实对象的包装层;

两者最重要的区别是

如果没有对mock对象打桩,那么调用mock对象的任何方法,都不执行真实逻辑,默认返回 null, 0,false

如果没有对spy对象打桩,那么调用spy对象的任何方法,都会默认执行真实逻辑

mock()方法定义:

java
public static <T> T mock(Class<T> classToMock) {
    // ...
}

public static <T> T mock(Class<T> classToMock, MockSettings mockSettings) {
    // ...
}

对于mock对象,只需要传入需要mock的类型,如果还有其他设置,可以通过MockSettings传入。

spy()方法定义:

java
public static <T> T spy(T object) {
    // ...
}

public static <T> T spy(Class<T> classToSpy) {
  	// ...
}

对于spy对象,有两种创建方式:

  • 包装现有实例:这是最经典的用法。需要先 new 一个真实对象,然后让 Mockito 给它套个“壳”。如果在 new 对象时通过构造函数传了参数,或者手动设置了属性,这些状态都会保留在 spy 对象中。

  • 凭空创建并调用构造函数:只需要传入需要spy对象的类型,然后默认调用该类的无参构造函数来实例化。如果该类没有无参构造函数,直接调用会报错:

    org.mockito.exceptions.base.MockitoException: Unable to create mock instance of type 'XXX'

例如,下面的例子演示了mock与spy最重要的区别:

java
public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}
java
public class CalculatorTest {
    
    @Test
    public void testAdd_spy() {
        Calculator spy = spy(Calculator.class);

        int result = spy.add(1, 2);

        System.out.println(result);
      	// 结果为3	
    }

    @Test
    public void testAdd_mock() {
        Calculator mock = mock(Calculator.class);

        int result = mock.add(1, 2);

        System.out.println(result);
      	// 结果为0
    }
}

2.3 打桩

打桩,解决“替身怎么表现”的问题,也就是说定义当调用替身对象的某个方法时,该方法应该执行什么逻辑、返回什么值。

在Mockito中,打桩主要分为两种风格:when(...).then... 风格(最常用)和 do...when(...) 风格(功能最全)。

TIP

注意,后打桩的会覆盖之前打桩的。

2.3.1 when(...).then... 风格

这是 Mockito 的标准语法,读起来就像英语句子:“当...的时候,然后...”。

返回固定值:

java
Calculator mock = mock(Calculator.class);
// 打桩
when(mock.add(1,2)).thenReturn(100);
int result = mock.add(1, 2);  // result为100
int result2 = mock.add(1, 1);  // result2为0,因为没有打桩

第三行即打桩,表示当调用mock.add(1,2)时,返回100。注意参数也要匹配上,并且不会执行mock.add()内部逻辑。

假设Calculator.add()新加一行:

java
public class Calculator {
    public int add(int a, int b) {
        System.out.println("add...");
        return a + b;
    }
}

是不会看到add...输出在控制台的。

抛出异常:

java
@Test
public void testAdd_mock_throw_exception(){
    Calculator mock = mock(Calculator.class);
		// 打桩,抛出异常
    when(mock.add(1,1)).thenThrow(new RuntimeException("参数错误"));

    Assertions.assertThrows(RuntimeException.class, () -> {
        mock.add(1,1);
    });
}

连续触发不同结果:模拟同一个方法被多次调用时产生不同反应

java
@Test
public void testAdd_mock_multi_invokes() {
    Calculator mock = mock(Calculator.class);

    when(mock.add(1,2))
            .thenReturn(1)
            .thenReturn(2)
            .thenThrow(new RuntimeException("参数错误"));

    int result = mock.add(1, 2);
    Assertions.assertEquals(1, result);
    int result2 = mock.add(1, 2);
    Assertions.assertEquals(2, result2);

    RuntimeException runtimeException = Assertions.assertThrows(
            RuntimeException.class,
            () -> mock.add(1, 2)
    );
    Assertions.assertEquals("参数错误", runtimeException.getMessage());
}

2.3.2 do...when(...) 风格

这种语法的顺序是反过来的。虽然读起来略显别扭,但在某些特定场景下,它是唯一的选择。

  1. 打桩 void 方法when(mock.voidMethod()) 在 Java 中无法编译,因为 when 需要一个返回值作为参数。
  2. 打桩 Spy 对象:为了避免执行真实的方法逻辑。

如果使用when...then风格打桩void方法:

java
public class Calculator {
    public void sub(int a, int b) {
        System.out.println("sub...");
        System.out.println(a - b);
    }
}
java
Calculator mock = mock(Calculator.class);
when(mock.sub(2,1)).thenThrow(new RuntimeException("参数错误"));

在编译期间就会报错。

使用do...when...风格修正如下:

java
@Test
void testSub_mock_return_void(){
    Calculator mock = mock(Calculator.class);
    doNothing().when(mock).sub(2,1);
    mock.sub(2,1);
}

如果在spy对象上使用when...then...风格打桩,那么会先触发调用一次真实逻辑,这次调用有很大的概率发生异常,造成测试失败。因此,对于spy对象,推荐使用do...when...方式打桩:

java
@Test
public void testAdd_spy() {
    Calculator spy = spy(Calculator.class);
    when(spy.add(1,2)).thenReturn(2);   // 这里会输出 add...
    int result = spy.add(1, 2);
    System.out.println(result);         // 这里会输出 2
}
java
@Test
public void testAdd_spy_do_when() {
    Calculator spy = spy(Calculator.class);
    doReturn(2).when(spy).add(1,2);  // 这里不会输出内容
    int result = spy.add(1, 2);
    System.out.println(result);    // 输出  2
}

2.3.3 参数匹配器

在上面的例子总,打桩时,我们都传入了具体的参数值,但实际上并不一定要传入精确的参数。Mockito 提供了强大的模糊匹配功能,在ArgumentMatchers类中,主要的有以下匹配器:

  • any():匹配任何参数;

  • any(Class<T> type):匹配任何属于类型type的参数,不包括null

  • nullable(Class<T> clazz):匹配任何属于类型clazz的参数,包括null

  • anyBoolean()/anyInt()等:匹配原始类型;

  • anyString():匹配任何字符串,不包括null

  • anyList()/anySet()/anyMap():匹配任何列表/集合/MAP;

  • isNull()/notNull():匹配为null/不为null的参数;

  • eq(T value):匹配等于value的参数;

  • argThat(ArgumentMatcher<T> matcher):自定义满足匹配条件的参数,返回null,适用于类;

    java
    public static <T> T argThat(ArgumentMatcher<T> matcher) {
        reportMatcher(matcher);
        return null;
    }
  • intThat(ArgumentMatcher<Integer> matcher):自定义满足匹配条件的整形,返回0;

    java
    public static int intThat(ArgumentMatcher<Integer> matcher) {
        reportMatcher(matcher);
        return 0;
    }

例如,下面的例子使用intThat(),匹配小于10的参数:

java
@Test
public void testAdd_spy_argThat() {
    Calculator spy = spy(Calculator.class);
    doReturn(2).when(spy).add(intThat(a -> a < 10), anyInt());
    int result = spy.add(1, 2);
    System.out.println(result);  // 结果为  2,满足打桩条件

    int result2 = spy.add(10, 2);
    System.out.println(result2);  // 结果为  12,不满足打桩条件,调用实际方法
}

CAUTION

注意,如果其中一个参数使用了匹配器,其他参数也要使用,下面的例子会报错:

java
doReturn(2).when(spy).add(intThat(a -> a < 10), 2);

org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers!
2 matchers expected, 1 recorded:

需要使用:

java
doReturn(2).when(spy).add(intThat(a -> a < 10), eq(2));

2.3.4 定制化返回

如果不想返回写死的数据,也可以根据输入参数动态计算返回值,甚至还可以调用原始方法,使用thenAnswer(),例如:

java
@Test
public void testAdd_mock_thenAnswer(){
    Calculator mock = mock(Calculator.class);

    when(mock.add(anyInt(),anyInt())).thenAnswer(invocation -> {
        Object argument1 = invocation.getArgument(0);
        Integer argument2 = invocation.getArgument(1, Integer.class);
        System.out.println(argument1 + "  " + argument2);
        Object realResult = invocation.callRealMethod();
        System.out.println(realResult);
        return realResult;
    });

    int result = mock.add(1, 1);
    System.out.println(result);
}

结果如下:

txt
1  1
add...
2
2
  • invocation.getArgument():获取到调用参数;
  • invocation.callRealMethod():调用真实方法,即使是在mock对象上,也能调用到真实方法;

如果mock的是接口,调用invocation.callRealMethod()时会报错:

org.mockito.exceptions.base.MockitoException:
Cannot call abstract real method on java object!
Calling real methods is only possible when mocking non abstract method.

2.4 验证

Mockito中的验证是指行为验证(Behavior Verification):确认某个 Mock 对象的某个方法,是否以预期的参数、预期的次数被调用了。主要使用verify()方法进行验证。

最基础的用法就是验证mock对象的某个方法是否以指定的参数被调用过:

java
Calculator spy = spy(Calculator.class);
doReturn(2).when(spy).add(intThat(a -> a < 10), eq(2));

int result = spy.add(1, 2);
Assertions.assertEquals(2,result);

// 验证
verify(spy).add(2,2);

如果验证失败,整个测试方法会失败,显示如下信息:

Argument(s) are different! Wanted:
calculator.add(2, 2);

verify()默认验证方法是否调用了一次,还可以验证调用次数,精确控制逻辑触发的频率。

  • 精确次数verify(mock, times(3)).method()(必须刚好 3 次)
  • 最少/最多atLeast(2) / atMost(5)
  • 从未调用never()(等同于 times(0)
  • 至少一次atLeastOnce()
java
// 验证调用了5次
verify(spy, times(5)).add(2,2);

// 验证从未被调用
verify(spy, never()).add(2,2);

还可以验证调用的顺序:

java
Calculator mock = mock(Calculator.class);
doNothing().when(mock).sub(2,1);
doNothing().when(mock).sub(3,4);
mock.sub(2,1);
mock.sub(3,4);

InOrder inOrder = Mockito.inOrder(mock);

inOrder.verify(mock).sub(3,4);
inOrder.verify(mock).sub(2,1);

// 异常:
// org.mockito.exceptions.verification.VerificationInOrderFailure: 
// Verification in order failure

在方法参数上,我们可以使用参数匹配器:

java
verify(spy, times(5)).add(anyInt(), anyInt());

如果参数比较复杂,我们可以使用参数捕获器,获取到实际调用的参数,根据参数来做判断:

java
@Test
public void testAdd_spy_captor() {
    Calculator spy = spy(Calculator.class);
    doReturn(2).when(spy).add(intThat(a -> a < 10), eq(2));

    // 调用两次add
    int result = spy.add(1, 2);
    result = spy.add(1, 2);
    Assertions.assertEquals(2,result);

    // 定义一个参数捕获器
    ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class);
    // 验证,使用参数捕获器获取调用参数
    verify(spy, atMost(5)).add(captor.capture(), captor.capture());

    // 获取捕获到的参数,进行断言
    // getValue 获取最后一个捕获到的参数
    Integer value = captor.getValue();
    System.out.println(value);  // 结果为 2
    List<Integer> allValues = captor.getAllValues();
    System.out.println(allValues); // 结果为 [1, 2, 1, 2]  ,因为调用了两次add

    // 断言参数
    Assertions.assertTrue(value < 10);
}

3. 高级用法

3.1 @Mock、@Spy、@InjectMocks

在前面的例子(基本使用小节)中,我们使用mock()spy()静态方法来创建测试替身,Mockito也提供了注解来创建测试替身:

  • @Mock:自动创建并注入mock对象;
  • @Spy:自动创建并注入spy对象;
  • @InjectMocks:自动为组建注入测试替身依赖;

注意,使用以上注解的前提是开启了相关配置,完整案例如下:

java
class OrderManagerTest {

    private AutoCloseable autoCloseable;

  	// 创建测试替身,并注入到字段mockExpress
    @Mock
    private ExpressApi mockExpress;
  
  	// 将测试替身 mockExpress 注入到manager中
    @InjectMocks
    private OrderManager manager中;

  	// 开启配置
    @BeforeEach
    void setUp() {
        autoCloseable = MockitoAnnotations.openMocks(this);
    }

  	// 关闭配置
    @AfterEach
    void tearDown() throws Exception {
        autoCloseable.close();
    }

    @Test
    void testPlaceOrder_Success() {
        // 创建测试替身已被注解替代
        
        // 打桩 (Stubbing)
        // 预设:只要调用接口,就返回假单号 "SF12345678"
        when(mockExpress.createOrder(anyString(), anyString()))
            .thenReturn("SF12345678");

        // 执行被测业务
        String result = manager.placeOrder("北京市朝阳区", "13800138000");

        // 验证 (Verify)
        // 验证返回结果是否包含 Mock 的单号,JUnit提供的功能
        assertEquals("ORDER_CREATED: SF12345678", result);
        
        // 验证业务系统是否真的去调了快递接口(Mockito提供的功能)
        verify(mockExpress).createOrder("北京市朝阳区", "13800138000");
    }
}

以上开启配置和关闭配置可以有JUnit的扩展机制完成。

首先,引入依赖:

xml
<groupId>org.mockito</groupId>
  <artifactId>mockito-junit-jupiter</artifactId>
  <version>5.22.0</version>
</dependency>

然后使用MockitoExtension扩展插件:

java
@ExtendWith(MockitoExtension.class)
class OrderManagerTest {

    private AutoCloseable autoCloseable;

    @Mock
    private ExpressApi mockExpress;
    @InjectMocks
    private OrderManager manager;

    @Test
    void testPlaceOrder_Success() {
        // 创建测试替身已被注解替代

        // 打桩 (Stubbing)
        // 预设:只要调用接口,就返回假单号 "SF12345678"
        when(mockExpress.createOrder(anyString(), anyString()))
            .thenReturn("SF12345678");

        // 执行被测业务
        String result = manager.placeOrder("北京市朝阳区", "13800138000");

        // 验证 (Verify)
        // 验证返回结果是否包含 Mock 的单号,JUnit提供的功能
        assertEquals("ORDER_CREATED: SF12345678", result);
        
        // 验证业务系统是否真的去调了快递接口(Mockito提供的功能)
        verify(mockExpress).createOrder("北京市朝阳区", "13800138000");
    }
}

3.2 @Captor

同样可以使用@Captor来定义参数捕获器:

java
@ExtendWith(MockitoExtension.class)
class OrderManagerTest {

    private AutoCloseable autoCloseable;

    @Mock
    private ExpressApi mockExpress;
    @InjectMocks
    private OrderManager manager;

    @Captor
    private ArgumentCaptor<String> stringArgumentCaptor;

    @Test
    void testPlaceOrder_Success() {
        // 创建测试替身已被注解替代

        // 打桩 (Stubbing)
        // 预设:只要调用接口,就返回假单号 "SF12345678"
        when(mockExpress.createOrder(anyString(), anyString()))
            .thenReturn("SF12345678");

        // 执行被测业务
        String result = manager.placeOrder("北京市朝阳区", "13800138000");

        // 验证 (Verify)
        // 验证返回结果是否包含 Mock 的单号,JUnit提供的功能
        assertEquals("ORDER_CREATED: SF12345678", result);
        
        // 验证业务系统是否真的去调了快递接口(Mockito提供的功能)
        verify(mockExpress).createOrder(stringArgumentCaptor.capture(), stringArgumentCaptor.capture());
        assertEquals("北京市朝阳区", stringArgumentCaptor.getAllValues().get(0));
        assertEquals("13800138000", stringArgumentCaptor.getAllValues().get(1));
    }
}

3.3 MockSettings

MockSettings能在创建mock对象时,根据自身需要,定制化mock对象行为,在创建mock对象时传入:

java
mock(Class<T> classToMock, MockSettings mockSettings)

通过 Mockito.withSettings()静态方法来创建MockSettings对象。

WARNING

注意,不要过多使用MockSettings,保持测试代码简单最重要。 如果在测试代码中过多使用了MockSettings,考虑重构代码。

常见的设置如下:

  • 给mock对象取名字:

    java
    mock(Calculator.class, withSettings().name("计算器"));
  • 额外实现接口:

    java
    Calculator calculator = mock(Calculator.class, 
                                 withSettings().extraInterfaces(Serializable.class));
    
    Assertions.assertTrue(calculator instanceof Serializable);
  • 启用序列化:默认的 Mock 对象是不可序列化的。如果业务逻辑会将mock对象存入 Redis ,不开启此项会抛出 NotSerializableException

    java
    mock(Calculator.class, withSettings().serializable());
  • 定制mock方法的默认行为:通常 Mock 对象的方法返回 null0。可以全局修改这个规则,比如让它默认调用真实方法,或者返回“聪明”的空值(RETURNS_SMART_NULLS):

    java
    // 设置mock对象调用真实方法
    Calculator calculator = mock(Calculator.class, 
                                 withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS));
    
    int add = calculator.add(1, 2);
    System.out.println(add);
  • 调用构造方法:当想 Mock 一个类但又想运行其构造函数里的逻辑时使用:

    java
    mock(Calculator.class, withSettings().useConstructor());

3.4 重置测试替身

我们可以使用Mockito.reset(T... mocks)来重置mock对象,移除打的桩:

java
@Test
public void test() {
    Calculator calculator = mock(Calculator.class);

    doReturn(1).when(calculator).add(1,2);

    // 重置mock对象
    reset(calculator);

    int add = calculator.add(1, 2);
    System.out.println(add);   // 结果为 0
}

3.5 mockStatic()

Mockito支持对静态方法进行打桩,例如:

java
public class StaticUtils {
    public static String getUrl()
    {
        return "http://xxx:80";
    }
}
java
@Test
public void test2() {
    // mock静态方法
    try (MockedStatic<StaticUtils> utilities = mockStatic(StaticUtils.class)) {
        utilities.when(StaticUtils::getUrl).thenReturn("http://localhost:8080");
        assertEquals("http://localhost:8080", StaticUtils.getUrl());
    }
    // 退出 try 块后,StaticUtils 恢复正常行为
    assertEquals("http://xxx:80", StaticUtils.getUrl());
}

3.6 BDD

BDD,全称Behavior-Driven Development,是指按照 // Given // When // Then 风格来编写测试代码的开发方式,例如:

java
@Test
public void test(){
  // Given
  
  // When
  
  // Then
}
  • Given:主要就是打桩
  • When:在mock/spy对象上调用方法
  • Then:验证

例如:

java
Seller seller = mock(Seller.class);
Shop shop = new Shop(seller);

public void shouldBuyBread() throws Exception {
 //given
 doReturn(new Bread()).when(seller).askForBread();

 //when
 Goods goods = shop.buyBread();

 //then
 assertThat(goods, containBread());
}

在第8行中,打桩我们使用了when(),但这属于// Given 板块,容易造成歧义,因此,Mockito提出了BDDMockito类,用来替代when()方法,如下:

java
BDDMockito.willReturn(new Bread()).given(seller).askForBread();
// 或者
BDDMockito.given(seller.askForBread()).willReturn(new Bread());

对于BDD风格的验证方法,也提出了then()方法,如下:

java
person.ride(bike);
person.ride(bike);

then(person).should(times(2)).ride(bike);
then(person).shouldHaveNoMoreInteractions();
then(police).shouldHaveZeroInteractions();

4. Mockito的限制

Mockito不支持对私有方法(private)的打桩。

这是 Mockito 团队的一种设计哲学。

  • 原因:Mockito 认为私有方法是类的内部实现细节。如果必须 Mock 私有方法才能进行测试,通常意味着类职责过重,应该重构(例如将私有逻辑抽离到另一个类的 public 方法中)。
  • 替代方案:如果非要 Mock,可以使用反射工具类,或者使用 PowerMock(但不推荐,因为 PowerMock 维护状态不佳且不支持 JUnit 5)。

Mockito也不支持hashCode()equals() 方法打桩。

  • 原因:Mockito 的内部集合和对象跟踪机制严重依赖这些方法。如果修改了它们的行为,Mockito 可能无法识别它自己创建的 Mock 对象,导致验证(verify)失效。
  • 替代方案:如果需要对象相等性逻辑,需要测试中创建真实的 DTO 对象,或者使用 ArgumentMatchers.argThat 来进行自定义属性比对。

参考资料

[1] Mockito官方网站:https://site.mockito.org/

[2] https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/InjectMocks.html

[3] https://dzone.com/refcardz/mockito

[4] 官方教程:https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html