美文网首页
Mockito 注解、插桩、验证、断言

Mockito 注解、插桩、验证、断言

作者: flyjar | 来源:发表于2025-08-05 11:28 被阅读0次

Mockito 注解、插桩、验证、断言、mockspy 等关键内容:

一、Mockito 基础与核心注解

1. 核心依赖与配置

  • 需在测试类上添加 @ExtendWith(MockitoExtension.class) 注解,启用 Mockito 扩展支持。
  • 常用注解:
    • @Mock:创建一个完全模拟的对象(虚拟对象,无真实逻辑)。
    • @InjectMocks:自动将 @Mock 标注的对象注入到被测试类中(依赖注入)。
    • @Spy:创建一个对真实对象的“监视”对象(默认执行真实方法,可部分插桩)。

二、Mock 与 Spy 的区别

特性 Mock 对象 Spy 对象
本质 完全虚拟的对象(无真实实现) 基于真实对象的监视(可复用真实逻辑)
默认行为 未插桩方法返回空值(null0 等) 未插桩方法执行真实对象的逻辑
适用场景 完全隔离外部依赖(如数据库、网络) 复用大部分真实逻辑,仅修改部分行为
插桩推荐方式 when(mock.method()).thenReturn(...) doReturn(...).when(spy).method()
副作用 无(不调用真实方法) 有(可能修改真实对象状态)

示例

@ExtendWith(MockitoExtension.class)
class MockSpyTest {
    @Mock
    private List<String> mockList; // 完全模拟的List
    
    @Spy
    private List<String> spyList = new ArrayList<>(); // 基于真实ArrayList的监视对象

    @Test
    void testMock() {
        // Mock默认返回空值,需手动插桩
        when(mockList.size()).thenReturn(10);
        assertEquals(10, mockList.size());
    }

    @Test
    void testSpy() {
        // Spy默认执行真实方法
        spyList.add("item");
        assertEquals(1, spyList.size()); // 真实逻辑:size=1
        
        // 插桩覆盖部分行为(推荐用doReturn)
        doReturn(100).when(spyList).size();
        assertEquals(100, spyList.size()); // 插桩生效
    }
}

三、插桩(Stubbing):预设方法行为

插桩是为模拟对象预设返回值、异常或自定义行为,确保测试不受外部依赖影响。

1. 基础插桩方式

  • when(...).thenReturn(...):适用于非 void 方法,简洁直观。

    // 基本用法
    when(mockService.findById(1L)).thenReturn(new User(1L, "Alice"));
    
    // 连续返回不同值
    when(mockIterator.next())
        .thenReturn("first")
        .thenReturn("second");
    
  • doReturn(...).when(...):适用于 spy 对象或避免触发真实方法的场景。

    doReturn("test").when(spyList).get(0); // 不执行真实get(0)
    
  • 模拟异常

    // 非void方法
    when(mockService.findById(99L)).thenThrow(new RuntimeException("未找到"));
    
    // void方法(必须用doThrow)
    doThrow(new IOException()).when(mockFile).delete();
    
  • 自定义行为(thenAnswer:基于输入参数动态返回结果。

    when(calculator.add(anyInt(), anyInt())).thenAnswer(invocation -> {
        int a = invocation.getArgument(0);
        int b = invocation.getArgument(1);
        return a + b;
    });
    

2. 特殊场景插桩

  • void 方法插桩:只能用 doXxx() 系列(doNothing()doThrow() 等)。

    doNothing().when(mockLogger).debug(anyString()); // 忽略void方法
    
  • 参数匹配器:忽略具体参数,使用 any()eq()gt() 等匹配任意参数。

    import static org.mockito.ArgumentMatchers.*;
    
    when(mockService.findByName(anyString())).thenReturn(new User()); // 任意字符串
    when(mockService.validate(eq(10), gt(0))).thenReturn(true); // 第一个参数=10,第二个>0
    

四、验证(Verify):检查方法调用行为

验证用于确认 mock/spy 对象的方法是否按预期被调用(次数、参数等)。

常用验证方法

// 验证方法被调用过1次(默认)
verify(mockList).add("item");

// 验证调用次数
verify(mockList, times(3)).add(anyString()); // 调用3次
verify(mockList, never()).remove(any()); // 从未调用

// 验证调用顺序
InOrder inOrder = inOrder(mockService, mockRepo);
inOrder.verify(mockService).update(any());
inOrder.verify(mockRepo).save(any()); // 确保update在save之前调用

// 验证无多余调用
verifyNoMoreInteractions(mockList);

五、断言(Assertion):验证结果正确性

结合 JUnit 5 的 Assertions 工具类,验证测试结果是否符合预期。

import static org.junit.jupiter.api.Assertions.*;

@Test
void testResult() {
    User user = userService.getUser(1L);
    
    assertNotNull(user); // 非空断言
    assertEquals("Alice", user.getName()); // 相等断言
    assertTrue(user.getAge() > 18); // 条件断言
    
    // 异常断言
    assertThrows(RuntimeException.class, () -> {
        userService.getUser(99L);
    });
}

六、综合示例

测试 OrderService 依赖 ProductDao 的场景:

public class OrderService {
    private final ProductDao productDao;
    
    public OrderService(ProductDao productDao) {
        this.productDao = productDao;
    }
    
    public double calculateTotal(List<Long> productIds) {
        double total = 0;
        for (Long id : productIds) {
            total += productDao.getPrice(id);
        }
        return total;
    }
}

@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
    @Mock
    private ProductDao productDao;
    
    @InjectMocks
    private OrderService orderService;

    @Test
    void calculateTotal_ShouldReturnSum() {
        // 1. 插桩:预设商品价格
        when(productDao.getPrice(1L)).thenReturn(100.0);
        when(productDao.getPrice(2L)).thenReturn(200.0);
        
        // 2. 执行测试
        double total = orderService.calculateTotal(List.of(1L, 2L));
        
        // 3. 断言结果
        assertEquals(300.0, total, 0.01);
        
        // 4. 验证调用
        verify(productDao, times(2)).getPrice(anyLong()); // 调用2次
    }
}

总结

  • @Mock@Spy:分别用于创建完全模拟对象和基于真实对象的监视对象,按需选择隔离或复用真实逻辑。
  • 插桩:通过 when().thenReturn()doReturn().when() 预设方法行为,控制依赖响应。
  • 验证:用 verify() 确认方法调用行为,确保逻辑流程正确。
  • 断言:结合 JUnit 5 验证结果,确认功能正确性。

掌握这些核心概念,可以编写简洁、可靠的单元测试,有效隔离外部依赖并验证代码逻辑。

相关文章

  • Mockito.verify

    在某些情况下,除了验证程序的执行结果,还需要对程序的行为进行断言。Mockito提供了verify的方法来支持这一...

  • 注解 2020-11-17

    概念 注解本身没有任何意义,单独的注解就是一种注释,需要结合反射、插桩等技术才有意义。 Java 注解(annot...

  • 2、Java 中的注解

    1、注解的作用或者意义是什么? 1、注解本身没有任何意义,单独的注解就是一种注释,他需要结合其他如反射、插桩等技术...

  • 3、注解与反射

    1、注解的作用或者意义是什么? 注解本身没有任何意义,单独的注解就是一种注释,他需要结合其他如反射、插桩等技术才有...

  • Android基础-Java注解

    注解的作用或意义 注解本身没有任何意义,单独的注解就是一种注释。需要结合其他如反射、插桩等技术才有意义。Java注...

  • 29.Android架构-注解与反射

    注解的作用或者意义是什么? 注解本身没有任何意义,单独的注解就是一种注释,他需要结合其他如反射、插桩等技术才有意义...

  • Java注解

    1.注解是什么,有什么意义 注解本身没有任何意义,单独的注解就是一种注释,他需要结合其他如反射、插桩等技术才有意义...

  • 2018-06-08 Mockito

    Mockito 初始化注解 背景:使用@Mock,@spy,@InjectMock等注解需要先初始化才能使用。 初...

  • Retrofit中的注解反射与动态代理

    1.注解的含义和应用场景 注解的作用或者意义:单独的注解是一种注释,他需要结合其他如反射、插桩的等技术才有意义元注...

  • 字节码插桩极简入门

    不写废话,帮助你快速理解应对面试 目录 什么是编译插桩插桩的应用场景插桩的工具 什么是编译插桩 用通俗的话来讲,插...

网友评论

      本文标题:Mockito 注解、插桩、验证、断言

      本文链接:https://www.haomeiwen.com/subject/bvfdojtx.html