单元测试框架 Mockito 注解 – @Mock, @Spy, @Captor, @InjectMocks

转载翻译自 https://howtodoinjava.com/mockito/mockito-annotations/

这篇 mockito 的教程文章能够帮助你了解更多 mockito 注解,比如 @Mock, @Spy, @Captor, @InjectMocks,来写出更好的单元测试。

1. Mockito 注解

1.1. @Mock

@Mock 注解被往往用来创建以及注入模拟实例。我们会用 mockito 框架创建一个模拟的实例类,而不是去真的创建需要的对象。

@Mock 注解也可以用 var somethingYouWantMock = Mockito.mock(classToMock) 这种函数方式赋值来替代,它们的效果是一样的,而用 @Mock 注解通常能能看起来代码 “整洁” 一点,因为我们不会想用一堆看起来一样的 Mockito.mock 函数在代码里重复得到处都是。

@Mock 注解的优势:

  • 能够快速创建测试所需的对象
  • 重复的模拟创建代码很少
  • 测试类可读性更好
  • 更容易看懂验证报错,因为注解的模拟类直接是类的属性,可以用字段名去赋予含义。

在下面给出的示例中,我们模拟了HashMap类。在实际测试中,我们将模拟实际的应用类。我们在map中放入一个键值对,然后验证,确认方法调用是在模拟的map实例上执行的。

@Mock
HashMap<String, Integer> mockHashMap;
 
@Test
public void saveTest()
{
    mockHashMap.put("A", 1);
     
    Mockito.verify(mockHashMap, times(1)).put("A", 1);
    Mockito.verify(mockHashMap, times(0)).get("A");
 
    assertEquals(0, mockHashMap.size());
}

译者补充:mock意思就是造一个假的模拟对象,不会去调用这个真正对象的方法,这个mock对象里的所有行为都是未定义的,属性也不会有值,需要你自己去定义它的行为。比如说,你可以mock一个假的size(), 使其返回100,但实际上并没有真的创建一个 size 为100的 Map)

...
    when(mockHashMap.size())
        .thenReturn(100);
    
    assertEquals(100, mockHashMap.size());
...

1.2. @Spy

@Spy注释用于创建一个真实对象并监视这个真实对象。@Spy对象能够调用所监视对象的所有正常方法,同时仍然跟踪每一次交互,就像我们使用mock一样,可以自己定义行为。

可以看到,在下面给的示例中,由于我们向它添加了一个 key-value 键值对,size 变成了 1。我们也能够得到真正的通过键 key 去拿到 value 值的结果。这在 Mock 注解的例子中是不可能的。

译者补充: 因为mock是模拟整个生成一个假对象,spy像是间谍潜伏在真实对象里去篡改行为。

@Spy
HashMap<String, Integer> hashMap;
 
@Test
public void saveTest()
{
    hashMap.put("A", 10);
     
    Mockito.verify(hashMap, times(1)).put("A", 10);
    Mockito.verify(hashMap, times(0)).get("A");
     
    assertEquals(1, hashMap.size());
    assertEquals(new Integer(10), (Integer) hashMap.get("A"));
}

@Mock和@Spy的区别

在使用@Mock时,mockito创建了类的一个基础套壳实例,完全用于跟踪与它的全部交互行为。这不是一个真正的对象,并且不维护状态,不存在更改。

当使用@Spy时,mockito创建一个类的真实实例,可以跟踪与它的每个交互行为,这个真实类能维护类状态的变化。

1.3 @Captor

@Captor注释用于创建ArgumentCaptor实例,该实例用于捕获方法参数值,来用于进一步做断言验证。

注意mockito使用参数类的equals()方法验证参数值是否相同。

@Mock
HashMap<String, Integer> hashMap;
 
@Captor
ArgumentCaptor<String> keyCaptor;
 
@Captor
ArgumentCaptor<Integer> valueCaptor;
 
@Test
public void saveTest() 
{
    hashMap.put("A", 10);
 
    Mockito.verify(hashMap).put(keyCaptor.capture(), valueCaptor.capture());
 
    assertEquals("A", keyCaptor.getValue());
    assertEquals(new Integer(10), valueCaptor.getValue());
}

1.4 @InjectMocks

在mockito中,我们需要创建被测试的类对象,然后插入它的依赖项(mock)来完全测试行为。因此,我们要用到 @InjectMocks 注释。

@InjectMocks 标记了一个应该执行注入的字段。Mockito会按照下面优先级通过构造函数注入setter注入属性注入,来尝试注入你标识的mock。如果上面三种任何给定的注入策略注入失败了,Mockito不会报错。

更多信息: Mock和@ initmock注释的区别

译者补充: @InjectMocks 一般是你要测的类,他会把要测类的mock属性自动注入进去。@Mock 则是你要造假模拟的类。

<think> 我们正在讨论的是Mockito框架的`@Mock`注解。用户的问题很明确:单独使用`@Mock`注解是否会生效? 根据Mockito的使用规则,`@Mock`注解本身只是一个标记,它并不会自动创建模拟对象。为了让`@Mock`注解生效,我们必须初始化这些注解。 初始化`@Mock`注解的方式通常有以下几种: 1. 在测试类上使用`@RunWith(MockitoJUnitRunner.class)`(JUnit4) 2. 在测试类上使用`@ExtendWith(MockitoExtension.class)`(JUnit5) 3. 在`@Before`方法中调用`MockitoAnnotations.openMocks(this)` 4. 使用`MockitoRule`(JUnit4) 如果没有进行初始化,那么被`@Mock`注解的字段将保持为null,使用时会抛出NullPointerException。 因此,单独使用`@Mock`注解(不进行初始化)是不会生效的。 下面我将详细解释并给出示例。 </think> ### `@Mock`注解单独使用是否会生效? **不会生效**。单独使用`@Mock`注解无法实现模拟效果,必须配合初始化机制才能生效,否则会导致`NullPointerException`。原因如下: #### 1. `@Mock`的核心机制 - `@Mock`仅完成**声明**:它标记需要模拟的字段,但不会自动创建模拟实例 - **必须初始化**:未初始化的`@Mock`字段保持`null`值,调用时将抛出空指针异常 - 初始化过程:通过反射将声明的字段替换为`Mockito.mock()`生成的代理对象 #### 2. 生效必需的初始化方式 ```java // 方式1:JUnit4 初始化器 @RunWith(MockitoJUnitRunner.class) // ✅ 自动扫描@Mock[^3] public class ServiceTest { @Mock DependencyService dependency; // 生效 } ``` ```java // 方式2:手动初始化 public class ServiceTest { @Mock DependencyService dependency; // 声明 @Before public void setup() { MockitoAnnotations.openMocks(this); // ✅ 使@Mock生效 } } ``` ```java // 方式3:JUnit5 扩展 @ExtendWith(MockitoExtension.class) // ✅ JUnit5专用[^3] public class ServiceTest { @Mock DependencyService dependency; // 生效 } ``` #### 3. 错误示例分析 ```java // ❌ 单独使用@Mock(无初始化) public class PaymentServiceTest { @Mock // 仅声明未初始化 InventoryService inventory; @Test public void testProcess() { inventory.checkStock(); // 抛出NullPointerException } } ``` **错误原因**: `inventory`未被实例化,默认值为`null` #### 4. 原理验证实验 ```java @Test public void testAnnotationEffect() { // 场景1:未初始化 class UninitializedTest { @Mock InventoryService inventory; } UninitializedTest test = new UninitializedTest(); assertNull(test.inventory); // ✅ 验证为null // 场景2:初始化后 class InitializedTest { @Mock InventoryService inventory; } InitializedTest test2 = new InitializedTest(); MockitoAnnotations.openMocks(test2); // 初始化 assertNotNull(test2.inventory); // ✅ 验证非空 assertTrue(Proxy.isProxyClass(test2.inventory.getClass())); // ✅ 验证代理对象 } ``` #### 5. 与其他注解的协同 | **注解** | **是否需初始化** | **作用** | |---------------|----------------|-----------------------------| | `@Mock` | 是 | 创建模拟对象 | | `@Spy` | 是 | 创建部分模拟的真实对象 | | `@Captor` | 是 | 捕获方法参数 | | `@InjectMocks`| 是(依赖其他注解) | 自动注入模拟对象到被测类 | > **最佳实践**:始终通过`@RunWith`, `@ExtendWith`或`openMocks()`初始化框架,避免隐藏的`NullPointerException`[^3] ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值