1. 概述

本文我们将学习 Mockito 中的几个常用注解:@Mock, @Spy, @Captor, 以及@InjectMocks

想要了解更多内容,请点击Mockito系列教程

2. 启用 Mockito

开始之前,我们需要先使 Mockito 注解生效,有几种方法:

2.1. MockitoJUnitRunner

方法一:在JUnit 上设置 MockitoJUnitRunner

@ExtendWith(MockitoExtension.class)
public class MockitoAnnotationUnitTest {
    ...
}

2.2. MockitoAnnotations.initMocks()

方法二:手动编码,调用 MockitoAnnotations.openMocks() 方法

@Before
public void init() {
    MockitoAnnotations.openMocks(this);
}

2.3. MockitoJUnit.rule()

最后, 我们可以使用 MockitoJUnit.rule():

public class MockitoAnnotationsInitWithMockitoJUnitRuleUnitTest {

    @Rule
    public MockitoRule initRule = MockitoJUnit.rule();

    ...
}

注意,这需要将rule 设置为 public

3. @Mock 注解

@Mock 是 Mockito 中用的最多的注解,我们用它来创建并注入mock对象,而不用手动调用 Mockito.mock 方法。

为了方便对比,下面这个例子中,我们先是手动mock一个ArrayList

@Test
public void whenNotUseMockAnnotation_thenCorrect() {
    List mockList = Mockito.mock(ArrayList.class);

    mockList.add("one");
    Mockito.verify(mockList).add("one");
    assertEquals(0, mockList.size());

    Mockito.when(mockList.size()).thenReturn(100);
    assertEquals(100, mockList.size());
}

然后我们通过 @Mock 注解的方式完成相同的工作:

@Mock
List<String> mockedList;

@Test
public void whenUseMockAnnotation_thenMockIsInjected() {
    mockedList.add("one");
    Mockito.verify(mockedList).add("one");
    assertEquals(0, mockedList.size());

    Mockito.when(mockedList.size()).thenReturn(100);
    assertEquals(100, mockedList.size());
}

4. @DoNotMock 注解

@DoNotMock 注解用来标记不要mock的类或接口

import org.mockito.exceptions.misusing.DoNotMock;

@DoNotMock(reason = "Use a real instance instead")
public abstract class NotToMock {
    // Class implementation
}

5. @Spy 注解

spy与mock的区别是,mock代理了目标对象的全部方法,spy只是部分代理

下面我们学习如何使用 @Spy 注解spy一个现有的对象实例。

我们先不用注解的方式,演示如何创建一个 spy List。

@Test
public void whenNotUseSpyAnnotation_thenCorrect() {
    List<String> spyList = Mockito.spy(new ArrayList<String>());

    spyList.add("one");
    spyList.add("two");

    Mockito.verify(spyList).add("one");
    Mockito.verify(spyList).add("two");

    assertEquals(2, spyList.size());

    Mockito.doReturn(100).when(spyList).size();
    assertEquals(100, spyList.size());
}

然后我们通过 @Spy 注解的方式完成相同的工作:

@Spy
List<String> spiedList = new ArrayList<String>();

@Test
public void whenUseSpyAnnotation_thenSpyIsInjectedCorrectly() {
    spiedList.add("one");
    spiedList.add("two");

    Mockito.verify(spiedList).add("one");
    Mockito.verify(spiedList).add("two");

    assertEquals(2, spiedList.size());

    Mockito.doReturn(100).when(spiedList).size();
    assertEquals(100, spiedList.size());
}

本例中,我们:

  • 调用真实spiedList.add() 方法,向 spiedList 中新增元素
  • 使用Mockito.doReturn() 修饰后,spiedList.size() 会返回 100 而非 2

6. @Captor 注解

接下来让我们看看如何使用 @Captor 注解创建 ArgumentCaptor 实例。

在下面的示例中,我们先不使用 @Captor 注解,手动创建一个 ArgumentCaptor

@Test
public void whenUseCaptorAnnotation_thenTheSame() {
    List mockList = Mockito.mock(List.class);
    ArgumentCaptor<String> arg = ArgumentCaptor.forClass(String.class);

    mockList.add("one");
    Mockito.verify(mockList).add(arg.capture());

    assertEquals("one", arg.getValue());
}

现在,让我们使用 @Captor 注解来创建 ArgumentCaptor

@Mock
List mockedList;

@Captor 
ArgumentCaptor argCaptor;

@Test
public void whenUseCaptorAnnotation_thenTheSam() {
    mockedList.add("one");
    Mockito.verify(mockedList).add(argCaptor.capture());

    assertEquals("one", argCaptor.getValue());
}

7. @InjectMocks 注解

现在我们来讨论如何使用 @InjectMocks 注解将mock字段自动注入到被测试对象中。

在下面的示例中,我们将使用 @InjectMocks 把mock的 wordMap 注入到 MyDictionary dic 中:

@Mock
Map<String, String> wordMap;

@InjectMocks
MyDictionary dic = new MyDictionary();

@Test
public void whenUseInjectMocksAnnotation_thenCorrect() {
    Mockito.when(wordMap.get("aWord")).thenReturn("aMeaning");

    assertEquals("aMeaning", dic.getMeaning("aWord"));
}

下面是 MyDictionary 类:

public class MyDictionary {
    Map<String, String> wordMap;

    public MyDictionary() {
        wordMap = new HashMap<String, String>();
    }
    public void add(final String word, final String meaning) {
        wordMap.put(word, meaning);
    }
    public String getMeaning(final String word) {
        return wordMap.get(word);
    }
}

8. 将Mock注入Spy中

与前面测试类似,我们可能想在spy中注入一个mock:

@Mock
Map<String, String> wordMap;

@Spy
MyDictionary spyDic = new MyDictionary();

然而,Mockito 并不支持将mock注入spy,因此下面的测试会出现异常:

@Test 
public void whenUseInjectMocksAnnotation_thenCorrect() { 
    Mockito.when(wordMap.get("aWord")).thenReturn("aMeaning"); 

    assertEquals("aMeaning", spyDic.getMeaning("aWord")); 
}

如果我们想在 spy 中使用 mock,可以通过构造函数手动注入 mock:

MyDictionary(Map<String, String> wordMap) {
    this.wordMap = wordMap;
}

现在需要我们手动创建spy,而不使用注释:

@Mock
Map<String, String> wordMap; 

MyDictionary spyDic;

@BeforeEach
public void init() {
    MockitoAnnotations.openMocks(this);
    spyDic = Mockito.spy(new MyDictionary(wordMap));
}

现在测试将通过。

9. 使用注解时遇到空指针

通常,当我们使用 @Mock@Spy 注解时,可能会遇到 NullPointerException 异常:

public class MockitoAnnotationsUninitializedUnitTest {

    @Mock
    List<String> mockedList;

    @Test(expected = NullPointerException.class)
    public void whenMockitoAnnotationsUninitialized_thenNPEThrown() {
        Mockito.when(mockedList.size()).thenReturn(1);
    }
}

大多数情况下,是因为我们没有启用 Mockito 注解。所以请查看我们第一节的内容,使用Mockito前别忘了先初始化。

10. 备注

最后,这里有一些关于 Mockito 注解的说明:

  • Mockito 的注解减少了重复的mock代码
  • 它们使测试更具可读性。
  • @InjectMocks 是注入 @Spy@Mock 实例所必需的。

11. 总结

在这篇简短的文章中,我们介绍了 Mockito 库中注解的基础知识。

本文中的代码可在 GitHub 上获取。

更多关于Mockio, 请前往我们的系列文章.


« 上一篇: Guava CharMatcher使用
» 下一篇: Java Scanner 使用