优秀的编程知识分享平台

网站首页 > 技术文章 正文

Mockito @Mock、@Spy、@Captor和@InjectMocks入门

nanyue 2024-08-15 08:15:25 技术文章 155 ℃

1. 概述

在本文中,我们将介绍 Mockito 库的以下注释:@Mock@Spy@Captor@InjectMocks

2. 启用模拟注释

在我们进一步讨论之前,让我们探索在 Mockito 测试中启用注释的不同方法。

2.1.MockitoJUnitRunner

第一个选择是用 MockitoJUnitRunner 注释 JUnit 测试:

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

2.2.MockitoAnnotations.openMocks()

或者,可以通过调用 MockitoAnnotations.openMocks() 以编程方式启用 Mockito 注释:

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

2.3.MockitoJUnit.rule()

可以使用 MockitoJUnit.rule():

public class MockitoAnnotationsInitWithMockitoJUnitRuleUnitTest {

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

    ...
}

在这种情况下,我们必须记住公开我们的规则。

3.@Mock注释

Mockito中使用最广泛的注释是@Mock。我们可以使用@Mock来创建和注入模拟实例,而无需手动调用 Mockito.mock。在下面的示例中,我们将手动创建一个模拟的 ArrayList,而不使用 @Mock 注释:

@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注释来监视现有实例。在下面的示例中,我们在不使用 @Spy 注释的情况下创建列表的spy:

@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注释:

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

请注意,和以前一样,我们如何在这里与spy互动,以确保它的行为正确。在此示例中,我们:

  • 使用真正的方法 spiedList.add() 将元素添加到 spiedList
  • 使用 Mockito.doReturn() 将方法 spiedList.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 
ArgumentCaptor argCaptor;

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

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

请注意,当我们取出配置逻辑时,测试如何变得更简单、更具可读性。

6.@InjectMocks注释

现在让我们讨论如何使用@InjectMocks注释将模拟字段自动注入测试对象。在下面的示例中,我们将使用 @InjectMocks 将模拟 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);
    }
}

7. 向spy注入模拟

与上面的测试类似,我们可能希望将模拟注入spy:

@Mock
Map<String, String> wordMap;

@Spy
MyDictionary spyDic = new MyDictionary();

但是,Mockito 不支持将模拟注入spy,以下测试会导致异常:

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

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

如果我们想将模拟与spy一起使用,我们可以通过构造函数手动注入模拟:

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

8. 使用注释时遇到 NPE

通常,当我们尝试实际使用带有@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 注释时,我们都必须采取额外的步骤并初始化它们,如前所述。

9. 注意事项

最后,这里有一些关于Mockito注释的注释:

  • Mockito的注释最大限度地减少了重复的模拟创建代码。
  • 它们使测试更具可读性。
  • @InjectMocks 对于注入@Spy@Mock实例都是必需的。

10. 结论

在本文中,我们解释了 Mockito 库中注释的基础知识。

最近发表
标签列表