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, 请前往我们的系列文章.