Mockito 进阶教程:静态方法、私有方法、Spy 的模拟技巧

前置条件:

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.5.1</version> 
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>4.5.1</version>
<scope>test</scope>
</dependency>

一、模拟静态方法 mockStatic()

用途:模拟工具类的静态方法
依赖:Mockito 3.4.0+ 和 mockito-inline
示例场景:模拟 StringUtils 工具类

// 工具类
public class StringUtils {
    public static boolean isEmpty(String str) {
        return str == null || str.trim().isEmpty();
    }
}

// 测试类
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;

@ExtendWith(MockitoExtension.class)
class StaticMockTest {
 @Test
    void testMockStatic() {
        // 1. 创建静态方法的模拟作用域
        try (MockedStatic<StringUtils> mockedStatic = Mockito.mockStatic(StringUtils.class)) {
            
            // 2. 设置模拟行为
            mockedStatic.when(() -> StringUtils.isEmpty("hello"))
                       .thenReturn(false);  // 模拟非空
            mockedStatic.when(() -> StringUtils.isEmpty(""))
                       .thenReturn(true);    // 模拟空

            // 3. 验证模拟行为
            assertFalse(StringUtils.isEmpty("hello"));
            assertTrue(StringUtils.isEmpty(""));

            // 4. 验证调用次数
            mockedStatic.verify(() -> StringUtils.isEmpty("hello"));
            mockedStatic.verify(() -> StringUtils.isEmpty(""), Mockito.times(1));
        }
        
        // 5. 作用域外恢复原始行为
        assertTrue(StringUtils.isEmpty("")); // 调用真实方法
    }
}

关键点

  • 使用 try-with-resources 确保模拟作用域安全关闭
  • 作用域内静态方法被模拟,作用域外自动恢复
  • 通过 verify 验证调用次数

二、通过反射模拟私有方法

用途:测试公共方法调用的私有逻辑
注意:优先重构代码,避免测试私有方法
示例场景:模拟 UserService 的私有方法

// 被测试类
public class UserService {
    public String getUserRole(String userId) {
        return isAdmin(userId) ? "ADMIN" : "USER"; // 调用私有方法
    }

    private boolean isAdmin(String userId) {
        // 真实逻辑(如查数据库)
        return "admin123".equals(userId);
    }
}

// 测试类
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.lang.reflect.Method;

@ExtendWith(MockitoExtension.class)
class PrivateMethodTest {

    @Test
    void testMockPrivateMethod() throws Exception {
        // 1. 创建 Spy 对象(部分模拟)
        UserService spyService = Mockito.spy(new UserService());

        // 2. 使用反射覆盖私有方法
        Method isAdminMethod = UserService.class
                .getDeclaredMethod("isAdmin", String.class);
        isAdminMethod.setAccessible(true); // 突破私有访问限制

        // 3. 修改私有方法行为
        Mockito.doReturn(true)
               .when(spyService)
               .isAdmin("user456"); // 注意:需编译器支持方法名引用

        // 4. 测试公共方法
        assertEquals("ADMIN", spyService.getUserRole("user456")); // 模拟返回 ADMIN
        assertEquals("USER", spyService.getUserRole("other"));    // 原始逻辑
    }
}

关键点:

  • 通过 spy() 创建对象代理
  • 反射设置私有方法可访问
  • 使用 doReturn().when() 避免调用真实方法

三、使用 @Spy 模拟真实对象

用途:部分模拟对象,保留部分真实逻辑
示例场景:模拟 PaymentProcessor 的部分方法

// 被测试类
public class PaymentProcessor {
    public String validateCard(String card) {
        return isValid(card) ? "VALID" : "INVALID";
    }

    public boolean isValid(String card) {
        // 复杂验证逻辑(如调用外部服务)
        return card.length() == 16;
    }
}

// 测试类
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class) // 启用 Mockito 注解
class SpyAnnotationTest {

    @Spy // 创建部分模拟对象
    PaymentProcessor paymentProcessor = new PaymentProcessor();

    @Test
    void testSpyAnnotation() {
        // 1. 保留真实方法
        assertTrue(paymentProcessor.isValid("1234567890123456"));

        // 2. 模拟特定方法
        doReturn(true).when(paymentProcessor).isValid("1111");

        // 3. 测试依赖方法
        assertEquals("VALID", paymentProcessor.validateCard("1111")); // 使用模拟
        assertEquals("INVALID", paymentProcessor.validateCard("short")); // 使用真实
    }
}

关键点:

  • @Spy 需配合 @ExtendWith(MockitoExtension.class) 使用
  • 默认调用真实方法,可对指定方法模拟
  • 用 doReturn().when() 避免真实方法被调用

实践建议

  • 避免通过反射模拟私有方法,可以通过调用外层的public方法,进行间接调用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值