Difference between @Mock, @InjectMocks and @Captor

2023-12-19 09:55:59 浏览数 (2)

启用Mockito注释

我们的第一个选择是使用MockitoJUnitRunner注释 JUnit 测试

代码语言:javascript复制
@ExtendWith(MockitoExtension.class)
public class MockitoAnnotationUnitTest {
    ...
}

请注意,要在测试执行期间启用 Mockito 注释, MockitoAnnotations.initMocks(this)必须调用静态方法。 为了避免测试之间的副作用,建议在每次测试执行之前执行此操作:

代码语言:javascript复制
@Before 
public void initMocks() {
    MockitoAnnotations.initMocks(this);
}

单元测试注意的点

在测试中发现一个特点,就是参数类型不会装箱操作,如果类型不匹配虽然不会报错,但是mock不会成功,mock成功只会是固定的执行某个类型的方法,估计是底层写死类型了。

// method

public ServerUser findById(final long id) {

// 单元测试示例

// 错误的示例

when(serverUserDao.findById(anyInt())).thenReturn(new ServerUser());

// 正确的示例

when(serverUserDao.findById(anyLong())).thenReturn(new ServerUser());

差异表

@Mock

@InjectMocks

@Mock 创建一个模拟。

@InjectMocks 创建该类的一个实例,并将使用 @Mock 注释创建的模拟注入到该实例中。

@Mock 用于创建支持要测试的类的测试所需的模拟。

@InjectMocks用于创建测试类中需要测试的类实例。

要使用 @Mock 注解来测试依赖关系的注解类。

当需要为给定类执行实际方法体时,使用@InjectMocks。

我们必须为模拟对象定义when-thenReturn 方法,以及在实际测试执行期间将调用哪些类方法。

当我们需要使用模拟对象初始化所有内部依赖项才能正确运行该方法时,请使用@InjectMocks。

@Mock注解

Mockito 中使用最广泛的注释是@Mock。我们可以使用@Mock来创建和注入模拟实例,而无需手动调用Mockito.mock

在下面的示例中,我们将手动创建一个模拟的ArrayList,而不使用@Mock注释:

代码语言:javascript复制
@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());
}复制

@InjectMocks注解

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

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

代码语言:javascript复制
@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类:

代码语言:javascript复制
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);
    }
}
复制

@Captor注解

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

在下面的示例中,我们将创建一个ArgumentCaptor而不使用@Captor注释:

代码语言:javascript复制
@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实例:

代码语言:javascript复制
@Mock
List mockedList;

@Captor 
ArgumentCaptor argCaptor;

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

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

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

使用SpringExtension配置单元测试

当不需要mock的时候,可以使用SpringExtension环境进行局部测试,这里有一个点就是,需要手动导入ContextConfiguration配置类,因为它不会去扫包,需要你指定包,因为使用的是spring的环境,或者也可以使用@Import(FileRecordDao.class)进行导入。

代码语言:javascript复制
@ContextConfiguration(classes = {FileRecordDao.class})
@ExtendWith(SpringExtension.class)
class FileRecordDaoTest {
    @Autowired
    private FileRecordDao fileRecordDao;

    /**
     * Method under test: {@link FileRecordDao#getInstance()}
     */
    @Test
    void testGetInstance() {
        FileRecordDao actualInstance = FileRecordDao.getInstance();
        assertSame(actualInstance, actualInstance.getInstance());
    }
}

最后

这里是关于 Mockito 注释的一些注意事项:

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

点赞关注评论一键三连,欢迎关注公众号【i查拉图斯特拉如是说】每周分享技术干货、开源项目、实战经验、国外优质文章翻译等,您的关注将是我的更新动力!

0 人点赞