1. 概述

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

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

2. 启用 Mockito 注解

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

2.1. MockitoJUnitRunner

方法一:在JUnit 上设置 MockitoJUnitRunner

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

2.2. MockitoAnnotations.initMocks()

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

@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 注解

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

为了方便对比,下面这个例子中,我们不用 @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. @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

5. @Captor 注解

Next let's see how to use the @Captor annotation to create an ArgumentCaptor instance.

In the following example, we'll create an ArgumentCaptor without using the @Captor annotation:

@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());
}

Now let's make use of @Captor for the same purpose, to create an ArgumentCaptor instance:

@Mock
List mockedList;

@Captor 
ArgumentCaptor argCaptor;

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

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

Notice how the test becomes simpler and more readable when we take out the configuration logic.

6. @InjectMocks 注解

Now let's discuss how to use the @InjectMocks annotation to inject mock fields into the tested object automatically.

In the following example, we'll use @InjectMocks to inject the mock wordMap into the 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"));
}

Here is the class 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. Injecting a Mock Into a Spy

Similar to the above test, we might want to inject a mock into a spy:

@Mock
Map<String, String> wordMap;

@Spy
MyDictionary spyDic = new MyDictionary();

However, Mockito doesn't support injecting mocks into spies, and the following test results in an exception:

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

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

If we want to use a mock with a spy, we can manually inject the mock through a constructor:

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

Instead of using the annotation, we can now create the spy manually:

@Mock
Map<String, String> wordMap; 

MyDictionary spyDic;

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

The test will now pass.

8. Running Into NPE While Using Annotation

Often we may run into NullPointerException when we try to actually use the instance annotated with @Mock or @Spy:

public class MockitoAnnotationsUninitializedUnitTest {

    @Mock
    List<String> mockedList;

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

Most of the time, this happens simply because we forget to properly enable Mockito annotations.

So we have to keep in mind that each time we want to use Mockito annotations, we must take the extra step and initialize them as we already explained earlier.

9. 备注

Finally, here are some notes about Mockito annotations:

  • Mockito's annotations minimize repetitive mock creation code.
  • They make tests more readable.
  • @InjectMocks is necessary for injecting both @Spy and @Mock instances.

10. 总结

In this brief article, we explained the basics of annotations in the Mockito library.

The implementation of all of these examples can be found over on GitHub. This is a Maven project, so it should be easy to import and run as it is.

Of course, for more Mockito goodness, have a look at the series here.