IMLC.ME

Mockito @Mock, @Spy, @Captor 简介

在 Java 的生态里,Mockito 可以说是单元测试比不可少的工具。在测试中,我们需要频繁地构造外部依赖类。通过控制外部依赖类的行为,来在单元测试中检查代码的不同运行路径。借助 Mockito,我们可以轻易地构造一个虚假的类实例,控制类方法的返回值、是否抛出异常等行为,又或者检查方法被调用了多少次,被调用时的输入参数的行为。

下面,让我来简单地介绍 Mockito 的通常用法。

构造返回值

@Test
void mockitoTest() {
  Document document = mock(Document.class);
  when(document.getName()).thenReturn("Mockito");
  assertThat(document.getName()).isEqualTo("Mockito");
}

根据不同入参返回不同结果

@Test
void mockitoTest() {
  Document document = mock(Document.class);
  when(document.getChapterName(0)).thenReturn("Overview");
  when(document.getChapterName(1)).thenReturn("Why and How");

  assertThat(document.getChapterName(0)).isEqualTo("Overview");
  assertThat(document.getChapterName(1)).isEqualTo("Why and How");
}

抛出异常

@Test
void mockitoTest() {
  Document document = mock(Document.class);
  when(document.getName()).thenThrow(new IllegalArgumentException("Oops!"));
  assertThatThrownBy(() -> {
    document.getName();
  })
      .isInstanceOf(IllegalArgumentException.class)
      .hasMessage("Oops!");
}

测试方法是否被调用

@Test
void mockitoTest() {
  Document document = mock(Document.class);
  document.getName();
  verify(document).getName();
}

检查方法是否被调用指定次数

@Test
void mockitoTest() {
  Document document = mock(Document.class);

  document.getName();
  document.getName();
  document.getName();

  verify(document, times(3)).getName(); // 测试 getName() 是否被调用3次
}

当你调用 verify() 方法而不带 times() 时,默认为 times(1)。

检查方法被调用时的入参

@Test
void mockitoTest() {
  Document document = mock(Document.class);
  document.getChapterName(9);
  verify(document).getChapterName(9);
}

检查方法的调用顺序

@Test
void mockitoTest() {
  Document document = mock(Document.class);

  document.getName();
  document.getChapterName(0);
  document.getChapterName(1);

  // 检查程序是否按 getName(), getChapterName(0), getChapterName(1)
  // 的顺序调用对应方法
  InOrder inOrder = inOrder(document);
  inOrder.verify(document).getName();
  inOrder.verify(document).getChapterName(0);
  inOrder.verify(document).getChapterName(1);
}

Spy

spy() 方法用于监控一个已有的实例。你不能像控制通过mock() 构造出来的实例一样控制其行为,但是你依旧可以 verify() 被监控实例的各种行为

@Test
void mockitoTest() {
  Document document = new Document();
  Document spyDocument = spy(document);
  spyDocument.getChapterName(0);
  verify(spyDocument).getChapterName(0);
}

Captor

Captor 用于捕获实例的传入参数。虽然 verify() 提供了检查方法入参的途径,但是有时候你会希望你能保存传入参数,方便后续更多的操作。例如,你要检查的传入参数是一个复杂的大对象。简单的一行 verify() 不能满足你的要求。又或者传入参数包含了一部分动态数据(例如当前时间的时间戳),你需要更复杂的测试逻辑而 verify() 不能满足要求。

不过下面的例子,为了方便,只以 String 为例。

@Test
void mockitoTest() {
  Document document = new Document();
  Document spyDocument = spy(document);
  spyDocument.setName("Cats and Dogs");

  ArgumentCaptor<String> arg = ArgumentCaptor.forClass(String.class);
  verify(spyDocument).setName(arg.capture());

  assertThat(arg.getValue()).isEqualTo("Cats and Dogs");
}