Mockito

常用注解

@Mock 模拟 创建一个对象,但无法进行实际操作
@InjectMocks 注射 用于mock时插入相应的依赖项
@Captor 捕获 用于捕获方法的参数值,来用于进一步做断言验证
@Spy 间谍 创建一个真实对象并监视这个对象

常用方法

doReturn() 返回 doReturn(值).when(条件);
doThrow() 抛异常 doThrow(异常).when(条件);
doAnswer() 回复 doAnswer(值).when(条件);
doNothing() 无操作 doNothing().when(条件);
doCallRealMethod() 使用真实方法操作 doCallRealMethod().when(对象).delete();

你需要对传递给方法的参数执行一些操作,例如,添加一些值,进行一些计算,甚至修改它们doAnswer给你提供了在方法被调用的瞬间执行的Answer接口,这个接口允许你通过InvocationOnMock参数与参数交互。另外,answer方法的返回值将是mocked方法的返回值。

initMocks						【初始化】MockitoAnnotations.initMocks(this);
assertThat						【断言】assertThat(实际值, is(理想值));
when							  【定义返回值】when(操作).thenReturn(值).thenReturn(值)

Mockito 中文文档 ( 2.23.4 )

本文档参考hehonghui/mockito-doc-zh项目,如有侵权,请联系删除

Mockito框架官方地址mockito文档地址

Mockito库能够Mock对象、验证结果以及打桩(stubbing)。

该文档您也可以通过http://mockito.org获取到。所有文档都保存在javadocs中,因为它能够保证文档与源代码的一致性。这样也能够让离线的用户从IDE直接访问到文档。这样一来也能够激励Mockito开发者在每次写代码、每次提交时更新对应的文档。

目录

  1. 迁移到Mockito 2.0
    0.1 Mockito支持Android
    0.2 免配置内联mock making
  2. 验证某些行为
  3. 如何做一些测试桩 (Stub)
  4. 参数匹配器 (matchers)
  5. 验证函数的确切、最少、从未调用次数
  6. 通过打桩为无返回值函数抛出异常
  7. 验证执行执行顺序
  8. 确认交互(interaction)操作没有在mock对象上执行
  9. 查找多余的调用
  10. 简化mock对象的创建 - @Mock注解
  11. 为连续的调用打桩 (迭代器风格的打桩)
  12. 通过回调方式来打桩
  13. doReturn()、doThrow()、doAnswer()、doNothing()、doCallRealMethod()系列方法
  14. 监控真实对象
  15. 修改没有测试桩的调用的默认返回值 ( 1.7版本之后 )
  16. 为下一步的断言捕获参数 (1.8版本之后)
  17. 真实的局部模拟对象(mocks) (1.8版本之后)
  18. 重置mocks对象 (1.8版本之后)
  19. 故障排查与验证框架的使用 (1.8版本之后)
  20. 行为驱动开发的别名 (1.8版本之后)
  21. 可序列化的mocks(1.8.1版本之后)
  22. 新的注解 : @Captor,@Spy,@InjectMocks (1.8.3版本之后)
  23. 带超时的验证 (1.8.5版本之后)
  24. 自动实例化被@Spies, @InjectMocks注释的字段以及构造函数注入 (1.9.0版本之后)
  25. 单行测试桩 (1.9.0版本之后)
  26. 验证被忽略的测试桩 (1.9.0版本之后)
  27. mock详情 (2.2.x中改进)
  28. 真实实例的委托调用 (1.9.5版本之后)
  29. MockMaker API (1.9.5版本之后)
  30. BDD风格的验证 (1.10.0版本之后)
  31. 监视 或 模拟 抽象类 (1.10.12版本加入,在2.7.13 和 2.7.14版特征得到增强))
  32. Mockito的模拟对象 可以通过 classloaders 序列化/反序列化(1.10.0版本之后)
  33. Deep stubs 更好的泛型支持 (1.10.0版本之后)
  34. Mockito JUnit 规则 (1.10.17版本之后)
  35. 开启和关闭插件的开关 (1.10.15版本之后)
  36. 自定义验证失败消息 (2.1.0版本之后)
  37. Java8 Lambda匹配器的支持 (2.1.0版本之后)
  38. Java8 自定义Answer的支持 (2.1.0版本之后)
  39. 元信息和泛型信息保留 (2.1.0版本之后)
  40. Mocking final类型,枚举和final方法 (2.1.0版本之后)
  41. “严格的”Mocktio能提高生产效率并使测试用例更清晰(2.+版本之后)
  42. 框架集成的高级公开API (2.10.+版本之后)
  43. 集成新的API: 监听验证开始(verification start)事件(2.11.+版本之后)
  44. 集成新的API: 测试框架支持MockitoSession(2.15.+版本之后)
  45. org.mockito.plugins.InstantiatorProvider泄露内部API所以被org.mockito.plugins.InstantiatorProvider2替代(2.15.4版本之后)
  46. JUnit5+的扩展
  47. 新的Mockito.lenient()和MockSettings.lenient()方法(2.20.0版本之后)

0. 迁移到Mockito 2.0

为了持续提升Mockito以及更进一步的提升单元测试体验,我们希望你升级到Mockito 2.0.Mockito遵循语意化的版本控制,除非有非常大的改变才会变化主版本号。在一个库的生命周期中,为了引入一系列有用的特性,修改已存在的行为或者API等重大变更是在所难免的。因此,我们希望你能够爱上 Mockito 2.0!

重要变更 :

  • Mockito从Hamcrest中解耦,自定义的matchers API也发生了改变,查看ArgumentMatcher 的基本原理以及迁移指南。

跟着我们的示例来mock 一个List,因为大家都知道它的接口(例如add(),get(), clear())。不要mock一个真实的List类型,使用一个真实的实例来替代。

0.1. Mockito支持Android

在2.6.1版本,我们提供了”原生的“Android支持。通过在项目中新增”mockito-android“库依赖,就能得到支持。这个工件发布到同一个Mockito组织,并且可以按照以下方式导入Android:

  repositories {
   
   
   jcenter()
 }
 dependencies {
   
   
   testCompile "org.mockito:mockito-core:+"
   androidTestCompile "org.mockito:mockito-android:+"
 }

通过在“testCompile”范围中使用“mockito-core”工件,可以在常规VM上继续运行相同的单元测试,如上所示。请注意,由于Android VM的限制,您不能在Android上使用内联模拟生成器。如果您在Android上遇到模拟的问题,请在官方问题跟踪器上打开一个issue。一定要提供您正在使用的Android版本和项目的依赖项。

0.2. 免配置内联mock making

从2.7.6版本开始,我们提供”mockito-inline“ artifact,它支持内联的mock making而不需要配置MockMaker的扩自文件。使用方式,用”mockito-inline“替换”mockito-core“:

 
 repositories {
   
   
   jcenter()
 }
 dependencies {
   
   
   testCompile "org.mockito:mockito-inline:+"
 }
 

注意,这个artifact可能会停止维护,当内联mock making特性被集成到默认的mock maker里时。
查看更多关于内联mock making,查看39章

1. 验证某些行为

 // 静态导入会使代码更简洁
 import static org.mockito.Mockito.*;

 // mock creation 创建mock对象
 List mockedList = mock(List.class);

 //using mock object 使用mock对象
 mockedList.add("one");
 mockedList.clear();

 //verification 验证
 verify(mockedList).add("one");
 verify(mockedList).clear();

一旦mock对象被创建了,mock对象会记住所有的交互动作。然后你就可以选择性的验证你感兴趣的交互动作。

2. 如何做一些测试桩 (Stub)

 // 你可以mock具体的类,不仅只是接口
 LinkedList mockedList = mock(LinkedList.class);

 // 测试桩
 when(mockedList.get(0)).thenReturn("first");
 when(mockedList.get(1)).thenThrow(new RuntimeException());

 // 输出“first”
 System.out.println(mockedList.get(0));

 // 抛出异常
 System.out.println(mockedList.get(1));

 // 因为get(999) 没有打桩,因此输出null
 System.out.println(mockedList.get(999));

 //虽然能验证被打桩对象的调用情况,但这通常写起来很啰嗦
 //如果你关注get(0)的返回值,那么其他地方就会中断(通常在verify()执行之前)
 //如果你不关注get(0)的返回值,那么它就不应该被打桩。不相信?看下面例子。
 verify(mockedList).get(0);
  • 默认情况下,所有的函数都有返回值。mock函数会适当的返回null,原始类型/原始类型的包装类,一个空的集合,例如int/Integer返回0、boolean/Boolean返回false;
  • 打桩动作可以被覆写 : 例如常见的打桩动作可以用于公共的配置,然后在测试函数中能够重新打桩。请注意,覆写测试桩函数是一种潜在的代码异味,它指出打桩动作太多了;
  • 一旦测试桩函数被调用,该函数将会一直返回固定的值;
  • 最后一个打桩动作是很重要的 - 当你用相同参数为一个方法做多次打桩时。换句话说:打桩的顺序很重要,但是这很少有意义,例如,当为完全相同的方法调用打桩,或者当使用参数匹配器时,等等。

3. 参数匹配器 (matchers)

Mockito以自然的java风格来验证参数值: 使用equals()函数。有时,当需要额外的灵活性时你可能需要使用参数匹配器,也就是argument matchers :


 // 使用内置的anyInt()参数匹配器来打桩
 when(mockedList.get(anyInt())).thenReturn("element");

 // 使用自定义的参数匹配器来打桩( 在isValid()函数中返回你自己的匹配器实现 )
 when(mockedList.contains(argThat(isValid()))).thenReturn("element");

 // 输出“element”
 System.out.println(mockedList.get(999));

 // 你也可以用参数匹配器来验证
 verify(mockedList).get(anyInt());
 
 //参数匹配器也能用Java 8 Lambda风格编写
 verify(mockedList).add(argThat(someString -> someString.length() > 5));

参数匹配器使验证和打桩变得更灵活。点击这里这里查看更多内置的匹配器以及自定义参数匹配器或者hamcrest 匹配器的示例。

如果仅仅是获取自定义参数匹配器的信息,查看ArgumentMatcher类文档即可。

为了合理的使用复杂的参数匹配,使用原生风格的equals()匹配器,偶尔也用下anyX()匹配器,会使得测试代码更简洁、简单。有时,会迫使你重构代码以使用equals()匹配或者实现equals()函数来帮助你进行测试。

同时建议你阅读第15章节或者ArgumentCaptor类文档。ArgumentCaptor是一个能够捕获参数值的特殊参数匹配器。

参数匹配器的注意点 :

如果你使用了参数匹配器, 那么所有参数都要用匹配器。

示例 : ( 该示例展示了如何多次应用于测试桩函数的验证 )

verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
// 上述代码是正确的,因为eq()也是一个参数匹配器

verify(mock).someMethod(anyInt(), anyString(), "third argument");
// 上述代码是错误的,因为所有参数必须由匹配器提供,而参数"third argument"并非由参数匹配器提供,因此会抛出异常

像anyObject(), eq()这样的匹配器函数不会返回匹配器。它们会在内部将匹配器记录到一个栈当中,并且返回一个假的值,通常为null。这样的实现是由于被Java编译器强加的静态类型安全检查。结果就是你不能在验证或者测试桩函数之外使用anyObject(), eq()函数。

4. 验证函数的确切、最少、从未调用的次数

 //使用mock对象
 mockedList.add("once");

 mockedList.add("twice");
 mockedList.add("twice");

 mockedList.add("three times");
 mockedList.add("three times");
 mockedList.add("three times");

 // 下面的两个验证函数效果一样,因为verify默认验证的就是times(1)
 verify(mockedList).add("once");
 verify(mockedList, times(1)).add("once");

 // 验证具体的执行次数
 verify(mockedList, times(2)).add("twice");
 verify(mockedList, times(3)).add("three times");

 // 使用never()进行验证,never相当于times(0)
 verify(mockedList, never()).add("never happened");

 // 使用atLeast()/atMost()进行验证
 verify(mockedList, atLeastOnce()).add("three times");
 verify(mockedList, atLeast(2)).add("five times");
 verify(mockedList, atMost(5)).add("three times");

verify函数默认验证的是执行了times(1),也就是某个测试函数是否执行了1次.因此,times(1)通常被省略了。

5. 通过打桩为无返回值函数抛出异常

doThrow(new RuntimeException()).when(mockedList).clear();

// 调用这句代码会抛出运行时异常
mockedList.clear();

关于doThrow|doAnswer 等函数族的信息请阅读第十二章节

6. 验证执行执行顺序

 // A. 一个方法必须以指定顺序执行的模拟的对象
 List singleMock = mock(List.class);

 //使用这个模拟的对象
 singleMock.add("was added first");
 singleMock.add("was added second");

 // 为该mock对象创建一个inOrder验证器
 InOrder inOrder = inOrder(singleMock);

 // 确保add函数首先执行的是add("was added first"),然后才是add("was added second")
 inOrder.verify(singleMock).add("was added first");
 inOrder.verify(singleMock).add("was added second");

 // B .验证多个mock对象的必须以指定顺序被调用
 List firstMock = mock(List.class);
 List secondMock = mock(List.class);

 //使用多个模拟的对象
 firstMock.add("was called first");
 secondMock.add("was called second");

 // 创建inOrder对象,将需要验证的模拟对象传进来
 InOrder inOrder = inOrder(firstMock, secondMock);

 // 下面将会确认第一个模拟对象会在第二个模拟对象前被调用
 inOrder.verify(firstMock).add("was called first");
 inOrder.verify(secondMock).add("was called second");

 // Oh, and A + B can be mixed together at will

验证执行顺序是非常灵活的 - 你不需要一个一个的验证所有交互,只需要验证你感兴趣的对象即可。
另外,你可以仅通过那些需要验证顺序的mock对象来创建InOrder对象。

7. 确认交互(interaction)操作没有在mock对象上执行

 // 使用Mock对象 - 只有 mockOne发生了交互
 mockOne.add("one");

 // 普通验证
 verify(mockOne).add("one");

 // 验证一个模拟对象没有执行某个方法
 verify(mockOne, never()).add("two");

 // 验证其他mock对象没有交互
 verifyZeroInteractions(mockTwo, mockThree);

8. 查找多余的调用

//using mocks
mockedList.add("one");
mockedList.add("two");

verify(mockedList).add("one");

// 下面的验证将会失败
verifyNoMoreInteractions(mockedList);

一些做了很多经典的、期望-执行-测试风格的模拟的用户,可能倾向于经常使用verifyNoMoreInteractions(),甚至在每个测试函数中都用。但是verifyNoMoreInteractions()并不建议在每个测试函数中都使用。verifyNoMoreInteractions()在交互测试套件中只是一个便利的验证,它的作用是当你需要验证是否存在冗余调用时。滥用它将导致测试代码的可维护性降低。你可以阅读这篇文档来了解更多相关信息。

也可以看看never(),它更加明确,并且很好地传达了意图。

9. 简化mock对象的创建 - @Mock注解

  • 最小化重复的创建代码
  • 使测试类的代码可读性更高
  • 使验证错误更易于阅读,因为字段名可用于标识mock对象
public class ArticleManagerTest {
   
   

   @Mock private ArticleCalculator calculator;
   @Mock private ArticleDatabase database;
   @Mock private UserProvider userProvider;

   private ArticleManager manager;

注意! 下面这句代码需要在运行测试函数之前被调用,一般放到测试类的基类或者test runner中:

 MockitoAnnotations.initMocks(testClass);

你可以使用内置的runner: MockitoJUnitRunner runner 或者一个rule : MockitoRule。关于JUnit5的测试案例,查看45章节关于JUnit扩展的描述。
关于mock注解的更多信息可以阅读MockitoAnnotations文档

10. 为连续的调用打桩 (迭代器风格的打桩)

有时我们需要为同一个函数调用的不同的返回值/异常做测试桩。典型的运用案例就是对迭代器的模拟。
原始版本的Mockito并没有这个特性来促进模拟行为更简单,例如,可以使用Iterable或者简单的集合来替换迭代器。这些方法提供了更自然的方式,在一些场景中为连续的调用做测试桩会很有用。示例如下 :

 when(mock.someMethod("some arg"))
   .thenThrow(new RuntimeException())
   .thenReturn("foo");

 // 第一次调用 : 抛出运行时异常
 mock.someMethod("some arg");

 // 第二次调用 : 输出"foo"
 System.out.println(mock.someMethod("some arg"));

 // 后续调用 : 也是输出"foo"
 System.out.println(mock.someMethod("some arg"));

另外,连续调用的另一种更简短的版本 :

// 第一次调用时返回"one",第二次返回"two",第三次返回"three"
 when(mock.someMethod("some arg"))
   .thenReturn("one", "two", "three");

注意:如果替代了“.thenReturn()"的调用,多个使用相同匹配器或参数的打桩动作,那么每个打桩动作都会覆盖之前的动作:

 //All mock.someMethod("some arg") calls will return "two"
 when(mock.someMethod("some arg"))
   .thenReturn("one");
 when(mock.someMethod("some arg"))
   .thenReturn("two");

11. 通过回调方式来打桩

允许通过泛型Answer接口进行打桩。

在最初的Mockito里也没有这个具有争议性的特性。我们建议使用thenReturn() 或thenThrow()来打桩。这两种方法足够用于测试或者测试驱动开发。
如果你需要用泛型Answer接口来打桩,下面是个例子:

 when(mock.someMethod(anyString())).thenAnswer(new Answer() {
   
   
     Object answer(InvocationOnMock invocation) {
   
   
         Object[] args = invocation.getArguments();
         Object mock = invocation.getMock();
         return "called with arguments: " + args;
     }
 });

 // 输出 : "called with arguments: foo"
 System.out.println(mock.someMethod("foo"));

12. doReturn()|doThrow()| doAnswer()|doNothing()|doCallRealMethod()系列方法

通过when(Object)为无返回值的函数打桩有不同的方法,因为编译器不喜欢void函数在括号内…

当你想为void函数打桩来跑出一个异常时,使用doThrow()方法:

doThrow(new RuntimeException()).when(mockedList).clear();

// 下面的代码会抛出运行时异常
mockedList.clear();

当你调用doThrow(), doAnswer(), doNothing(), doReturn() and doCallRealMethod() 这些函数时,可以在适当的位置调用when()函数. 当你需要下面这些功能时这是必须的:

  • 测试void函数
  • 在受监控的对象上测试函数
  • 同一个函数多次打桩,为了在测试过程中改变mock对象的行为。

但是在调用when()函数时你可以选择是否调用这些上述这些函数。

阅读更多关于这些方法的信息:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值