Mockito注解的基础知识

单元测试 Mar 01, 2020 阅读

本教程重点介绍了Mockito从基本到更高级的用例,以及与其他的测试工具(如Junit)的集成。
原文:https://www.baeldung.com/mockito-annotations
翻译:杨金龙

Mockito 基础

  • 使用Mockito @Mock@Spy@Captor@InjectMocks

1. 概述

本章节介绍Mockito的注解,@Mock, @Spy, @Captor, and @InjectMocks。

2. 启用Mockito注解

我们先来探索一下使用注解启用Mockito的几种方式:

2.1 MockitoJUnitRunner

第一种方式是在JUnit测试中使用注解@RunWith(MockitoJUnitRunner.class),如下所示:

@RunWith(MockitoJUnitRunner.class)
public class MockitoAnnotationTest {
    ...
}

2.2 MockitoAnnotations.initMocks()

可以通过调用MockitoAnnotations.initMocks()启用Mockito, 如下所示:

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

2.3 MockitoJUnit.rule()

还可以使用MockitoJUnit.rule(),如下所示

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

    ...
}

这种情况下,rule必须是public的。

3. @Mock 注解

在Mockito中,使用最广泛的注解是@Mock,可以使用@Mock注解创建和注入模拟实例,不需要手动调用Mockito.mock。

在下面的示例中,我们将使用手动的方式创建一个模拟的ArrayList(不使用@Mock注解):

@Test
public void whenNotUseMockAnnotationThenCorrect() {
    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 whenUseMockAnnotationThenMockIsInjected() {
    mockedList.add("one");
    Mockito.verify(mockedList).add("one");
    assertEquals(0, mockedList.size());

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

注意在上面两个示例中,我们是如何与模拟对象进行交互并且验证其中的一些交互的,只是为了确保模拟对象的行为是正确的。

4. @Spy 注解

现在,我们来看看如何使用@Spy注解来监视现有的实例。

在下面的实例中,我们使用旧方式创建一个监视List(不使用@Spy

@Test
public void whenNotUseSpyAnnotationThenCorrect() {
    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注解,对spyList执行同样的监视操作

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

@Test
public void whenUseSpyAnnotation_thenSpyIsInjectedCorrecttly(){
    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());
}

注意,就像之前描述的,我们在这里和监视对象进行交互,以确保它正确的工作,在这个例子中,

  • 我们使用了真实的spyList.add()方法将元素添加到spyList
  • 使用Mockito.doReturn()来将spyList.size()返回值返回100来替代原本的长度2

5. @Captor注解

接下来,我们学习如何使用@Captor注解来创建一个ArgumentCaptor实例。

首先,我们先在不使用@Captor注解的情况下,创建一个ArgumentCaptor

@Test
public void whenNotUseCaptorAnnotation_thenCorrect(){
    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
ArgumentCapror argCaptor;

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

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

注意当去掉配置逻辑后,测试是如何变得更简单和更易读的。

6. @InjectMocks注解

现在,我们讨论一下如何使用@InjectMocks注解将模拟字段自动的注入到测试对象中。

In the following example – we use @InjectMocks to inject the mock wordMap into the MyDictionary dic:
在下面的例子中,我们用@InjectMocks注解将模拟的wordMap注入到 MyDictionary dic

@Mock
Map<String, String> wordMap;

@InjectMocks
MyDictionary dict = new MyDictionary();

@Test
public void whenUseInjectMocksAnnotation_thenCorrect(){
    Mockito.when(wordMap.get("aWord")).thenRetuen("aMeaning");
    assertEquals("aMeaning", dict.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);
    }
}

7. 将Mock对象注入Spy

类似于上面的测试,我们可能需要给一个Spy对象注入一个Mock对象:

@Mock
Map<String, String> wordMap;

@Spy
MyDictionary spyDict = new MyDictionary();

然而Mockito并不支持向Spy对象注入Mock,下面的测试结果是一个例外:

@Test
public void whenUseInjectMocksAnnotation_thenCorrect(){
    Mockito.when(wordMap.get("aWord")).thenReturn("aMeaning");
    assertEquals("aMeaning", spyDict.getMeaning("aWord"));
}

如果我们想在Spy对象中使用Mock对象,可以通过构造函数手动注入Mock对象:

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

现在手动创建Spy对象,不需要用注解:

@Mock
Map<String, String> wordMap;

MyDictionary spyDict;

@Before
public void init(){
    MocitioAnnotations.initMocks(this);
    spyDict = Mockito.spy(new MyDictionay(wordMap));
}

8. 使用Mockito注解时的空指针异常

我们实际使用带有@Mock@Spy注解的实例时,可能会遇到NullPointerException

public class MockitoAnnotationUninitializedUnitTest{
    @Mock
    List<String> mockedList;

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

大多数情况下,仅是因为我们忘记正确启用 Mockito 注解。

所以我们必须记住,每次想要使用 Mockito 注解时,必须采取额外的步骤并初始化它们,初始化的步骤在前面的第2小节。

9. 笔记

最后,是一些关于 Mockito 注解的笔记:

  • Mockito 注解简化了创建mock对象,使代码的重复性最小化
  • 它们使测试更具可读性
  • @InjectMocks对于同时注入@Spy和@Mock实例是必需的

10. 总结

在这个快速教程中,我们展示了 Mockito 中注解的基础知识。

所有这些例子的实现都可以在 GitHub 上找到。 这是一个 Maven 项目,很容易导入和运行。

学习更多关于 Mockito 的教程,可以到这里

(全文完)