Mockito5.2.0学习

8 篇文章 2 订阅

Mockito是mocking框架,它让你用简洁的API做测试。而且Mockito简单易学,它可读性强和验证语法简洁。
Mockito 是一个针对 Java 的单元测试模拟框架,它与 EasyMock 和 jMock 很相似,都是为了简化单元测试过程中测试上下文 ( 或者称之为测试驱动函数以及桩函数 ) 的搭建而开发的工具

相对于 EasyMock 和 jMock,Mockito 的优点是通过在执行后校验哪些函数已经被调用,消除了对期望行为(expectations)的需要。其它的 mocking 库需要在执行前记录期望行为(expectations),而这导致了丑陋的初始化代码。
 

以下学习内容基于Mockito官方5.2.0文档中文文档2.23.4

目录

1. 验证某些行为

2. 打桩测试

3. 参数匹配器 (matchers)

  内置常用匹配器在org.mockito.ArgumentMatchers里

  附加匹配器在org.mockito.AdditionalMatchers里

4. 验证函数的调用次数

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

6. 验证执行顺序

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

8. 查找多余的调用

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

10. 为连续的调用打桩,多次调用返回不同的值

11. 通过回调方式来打桩

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

13. 监控真实对象

14. 修改没有测试桩的调用的默认返回值 ( 1.7版本之后 )

15. 为下一步的断言捕获参数 (1.8版本之后)

16. 真实的局部模拟对象(mocks) (1.8版本之后)

17. 重置mocks对象 (1.8版本之后)

18. 故障排查与验证框架的使用 (1.8版本之后)

19.行为驱动开发的别名 (1.8版本之后)

20. 可序列化的mocks(1.8.1版本之后)

21. 新的注解 : @Captor,@Spy,@InjectMocks (1.8.3版本之后)

22. 带超时的验证 (1.8.5版本之后)

23. 自动实例化被@Spy, @InjectMocks注解的字段以及构造函数注入 (1.9.0版本之后)

24. 单行测试桩 (1.9.0版本之后)

25. 验证被忽略的测试桩 (1.9.0版本之后)

26. mock详情 (2.2.x中改进)

27. 真实实例的委托调用 (Since 1.9.5)

28. MockMaker API (Since 1.9.5)

29. BDD 风格的验证 (Since 1.10.0)

30. 监视 或 模拟 抽象类 (1.10.12版本加入,在2.7.13 和 2.7.14版特征得到增强))

31. Mockito的模拟对象 可以通过 classloaders 序列化/反序列化 (Since 1.10.0)

32. Deep stubs 更好的泛型支持 (Since 1.10.0)

33. Mockito JUnit rule (Since 1.10.17)

34. 开启和关闭插件的开关 (Since 1.10.15)

35. 自定义验证失败信息 (Since 2.1.0)

36. Java 8 Lambda匹配器的支持 (Since 2.1.0)

37. Java 8 自定义Answer的支持 (Since 2.1.0)

38. 元数据和泛型信息保留 (Since 2.1.0)

39. 模拟final类型,枚举 和 final方法 (Since 2.1.0)

40. “严格的”Mocktio能提高生产效率并使测试用例更清晰(2.+版本之后)

41. 框架集成的高级公开API (2.10.+版本之后)

42. 用于集成的新API: 监听验证开始(verification start)事件(2.11.+版本之后)

43. 用于集成的新API: 测试框架支持MockitoSession(2.15.+版本之后)

44. org.mockito.plugins.InstantiatorProvider泄露内部API所以被org.mockito.plugins.InstantiatorProvider2替代

45. 新的JUnit Jupiter (JUnit5+) extension

46. 新的Mockito.lenient()和MockSettings.lenient()方法(2.20.0版本之后)

47. 用于在内联mocking中清除mock状态的新API(2.25.0版本之后)

48. 模拟静态方法(3.4.0版本之后)

49. 模拟对象构造(3.5.0版本之后)

50. 仅模拟接口时避免代码生成(3.12.2版本之后)

51. 把类标记为不可模拟(4.1.0版本之后)

52. @Mock注解和MockSettings.strictness()方法的新strictness属性(4.6.0版本之后)

53. 指定单个mock的mock maker(4.8.0版本之后)

54. mock/spy不指定类(4.9.0版本之后)


1. 验证某些行为

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

    // 静态导入会使代码更简洁
    import static org.mockito.Mockito.*;
    // 创建mock对象
    List mockedList = mock(List.class);
    // 使用mock对象
    mockedList.add("one");
    mockedList.clear();
    // 验证
    verify(mockedList).add("one");
    verify(mockedList).clear();

2. 打桩测试

  默认情况下,所有的函数都有返回值。
  mock函数会适当的返回null/原始类型/原始类型的包装类/一个空的集合,例如int/Integer返回0、boolean/Boolean返回false。

  打桩动作可以被覆写 : 例如常见的打桩动作可以用于公共的配置,然后在测试函数中能够重新打桩。
  请注意,覆写测试桩函数可能意味着打桩动作太多了。

  一旦测试桩函数被调用,该函数将会一直返回固定的值。
  多次使用同一参数对同一函数打桩时,调用时返回最后一次打桩的值。

    // 你可以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);

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

  由于Mockito any(Class)和anyInt家族匹配器执行类型检查,因此它们不会匹配null参数。请改用isNull匹配器。

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

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

  内置常用匹配器在org.mockito.ArgumentMatchers里

    匹配所有东西,包括null和可变参数
      any()
    匹配给定类型的对象,不包括null
      any​(Class<T> type)
    匹配基本数据类型或者非null 的基本数据类型对应的对象
      anyBoolean(),anyByte(),anyChar(),anyDouble(),anyFloat(),anyInt(),anyLong(),anyShort()
    匹配非null的常用类的对象
      anyCollection(),anyIterable(),anyList(),anyMap(),anySet(),anyString()
    使用用户自定义参数匹配器
      argThat​(ArgumentMatcher<T> matcher)
      booleanThat​(ArgumentMatcher<Boolean> matcher)
      byteThat​(ArgumentMatcher<Byte> matcher)
      charThat​(ArgumentMatcher<Character> matcher)
      doubleThat​(ArgumentMatcher<Double> matcher)
      floatThat​(ArgumentMatcher<Float> matcher)
      intThat​(ArgumentMatcher<Integer> matcher)
      longThat​(ArgumentMatcher<Long> matcher)
      shortThat​(ArgumentMatcher<Short> matcher)
    匹配相等
      eq​(boolean value),eq​(byte value),eq​(char value),eq​(double value),eq​(float value),eq​(int value),eq​(long value),eq​(short value),eq​(T value)
    匹配字符串
      contains​(String substring),endsWith​(String suffix),matches​(String regex),matches​(Pattern pattern),startsWith​(String prefix)
    NULL判断
      isNull(),isNull​(Class<T> type),isNotNull(),isNotNull​(Class<T> type),notNull(),notNull​(Class<T> type)
    实现给定类的对象参数
      isA​(Class<T> type)
    匹配null或者给定的类型
      nullable​(Class<T> clazz)
    反射后与给定的对象参数一致,支持从类中排除给定字段
      refEq​(T value, String... excludeFields)
    匹配相同引用
      same​(T value)

  附加匹配器在org.mockito.AdditionalMatchers里

    用于组合常用匹配器

      //anything but not "ejb"
      mock.someMethod(not(eq("ejb")));
      //not "ejb" and not "michael jackson"
      mock.someMethod(and(not(eq("ejb")), not(eq("michael jackson"))));
      //1 or 10
      mock.someMethod(or(eq(1), eq(10)));

    两个匹配器同时成立,T可以是对象类型或基本数据类型
      and​(T first, T second)
    两个匹配器任一成立,T可以是对象类型或基本数据类型
      or​(T first, T second)
    匹配器不成立,T可以是对象类型或基本数据类型
      not​(T first)
    匹配器返回值比较大小,T可以是对象类型或基本数据类型
      geq​(T value),gt​(T value),leq​(T value),lt​(T value)
    匹配器返回的数组值与给定值相等,T可以是对象类型或基本数据类型
      aryEq​(T[] value)
    使用绝对值差小于某个值判定浮点数相等
      eq​(double value, double delta)
      eq​(float value, float delta)
    使用compareTo比较相等
      cmpEq​(T value)
    匹配器返回值包含指定字符串
      find​(String regex)

4. 验证函数的调用次数

   调用次数是使用匹配参数列表的函数调用的次数,参数列表不匹配时,不计次数

   // 下面的两个验证函数效果一样,因为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");

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

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

6. 验证执行顺序

   // 为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");

   // 创建inOrder对象,将需要验证的mock对象传进来
   InOrder inOrder = inOrder(firstMock, secondMock);
   // 下面将会确认第一个模拟对象会在第二个模拟对象前被调用
   inOrder.verify(firstMock).add("was called first");
   inOrder.verify(secondMock).add("was called second");

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

   verifyNoInteractions​(mockTwo, mockThree);

8. 查找多余的调用

   // 调用mock对象
   mockedList.add("one");
   mockedList.add("two");
   // 验证执行了add("one")
   verify(mockedList).add("one");
   // 因为执行了未验证的add("two"),所以这里会报错
   verifyNoMoreInteractions(mockedList);

   不建议在每个测试函数中都使用verifyNoMoreInteractions()。
   在交互测试套件中只是一个便利的验证,它的作用是当你需要验证是否存在冗余调用时,滥用它将导致测试代码的可维护性降低。

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

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

    public class ArticleManagerTest {
    
        @Mock private ArticleCalculator calculator;
        @Mock private ArticleDatabase database;
        @Mock private UserProvider userProvider;
    
        private ArticleManager manager;
    
        @org.junit.jupiter.api.Test
        void testSomethingInJunit5(@Mock ArticleDatabase database) {

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

     MockitoAnnotations.initMocks(testClass);

10. 为连续的调用打桩,多次调用返回不同的值

  有时我们需要为同一个函数调用的不同的返回值/异常做测试桩。典型的运用案例就是对迭代器的模拟。

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

11. 通过回调方式来打桩

  允许通过泛型Answer接口进行打桩。
  在最初的Mockito里也没有这个具有争议性的特性。我们建议使用thenReturn() 或thenThrow()来打桩。这两种方法足够用于测试或者测试驱动开发。 

    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对象的行为。

13. 监控真实对象

  你可以为真实对象创建多个监控(spy)对象。
  当使用这个spy对象时,真实的方法会被调用(除非它的函数被stub了)。
  尽量少使用spy对象,使用时也需要小心形式,例如spy对象可以用来处理遗留代码。

  监控一个真实的对象可以与“局部mock对象”概念结合起来。
  在1.8之前,mockito的监控功能并不是真正的局部mock对象。原因是我们认为局部mock对象的实现方式并不好。
  在某些时候,我们发现了使用局部mock对象的合法用例。(第三方接口、临时重构遗留代码)

    List list = new LinkedList();
    List spy = spy(list);
    //可选的,你可以为某些函数打桩:
    when(spy.size()).thenReturn(100);
    //通过spy对象调用真实对象的函数
    spy.add("one");
    spy.add("two");
    //打印第一个元素"one"
    System.out.println(spy.get(0));
    //size()函数被打桩了,因此这里打印的是100
    System.out.println(spy.size());
    //可选的,你可以做校验
    verify(spy).add("one");
    verify(spy).add("two");

  有时,在监控对象上使用when(Object)来进行打桩是不可能或者不切实际的。因此,当使用监控对象时请考虑doReturn|Answer|Throw()函数族来进行打桩。

    List list = new LinkedList();
    List spy = spy(list);
    
    //不可能 : 当调用spy.get(0)时会调用真实对象的get(0)函数,此时会发生IndexOutOfBoundsException异常(因为真实list对象还是空的)
    when(spy.get(0)).thenReturn("foo");
    
    //你必须使用doReturn()来打桩
    doReturn("foo").when(spy).get(0);

  Mockito并不会代理传递来的真实对象,实际上它会拷贝一份真实对象。
  因此如果你保留了真实对象并且与之交互,不要期望从监控对象得到正确的结果。
  当你在监控对象上调用一个没有被stub的函数时,并不会调用真实对象的对应函数,你不会在真实对象上看到任何效果。
 

  注意final方法,Mockito不能mock final方法,所以底线是:当你监视真实对象,你尝试在fianl方法上打桩,那么会有问题。同样,你也不能验证这些方法。

14. 修改没有测试桩的调用的默认返回值 ( 1.7版本之后 )

  你可以创建mock对象,用指定策略来作为它的返回值。这是一个高级特性,通常来说,你不需要写这样的测试。
  无论如何,它对于遗留系统来说是很有用处的。
  当你没有为函数调用打桩时,你可以指定一个默认的answer。

    Foo mock = mock(Foo.class, Mockito.RETURNS_SMART_NULLS);
    Foo mockTwo = mock(Foo.class, new YourOwnAnswer());

  RETURNS_DEFAULTS
    首先尝试全局配置,如果没有全局配置,那么它将使用返回零、空集合、null等的默认答案。
  RETURNS_SMART_NULLS
    首先尝试返回普通值(零、空集合、空字符串等),然后尝试返回SmartNull。如果返回类型是final,则返回纯null。
  RETURNS_MOCKS
    首先尝试返回普通值(零、空集合、空字符串等),然后尝试返回mock对象。如果返回类型不能被模拟(例如是final),则返回纯null。
  RETURNS_DEEP_STUBS

   // 这样
   Foo mock = mock(Foo.class, RETURNS_DEEP_STUBS);
   when(mock.getBar().getName(), "deep");
   // 等价于下面:
   Foo foo = mock(Foo.class);
   Bar bar = mock(Bar.class);
   when(foo.getBar()).thenReturn(bar);
   when(bar.getName()).thenReturn("deep");

15. 为下一步的断言捕获参数 (1.8版本之后)

  Mockito以java代码风格的形式来验证参数值 : 即通过使用equals()函数。
  这也是我们推荐用于参数匹配的方式,因为这样会使得测试代码更简单、简洁。
  在某些情况下,当验证交互之后要检测真实的参数值时这将变得有用。例如 :

    ArgumentCaptor<Person> argument = ArgumentCaptor.forClass(Person.class);
    //参数捕获
    verify(mock).doSomething(argument.capture());
    //使用equal断言
    assertEquals("John", argument.getValue().getName());

  警告 : 我们建议在验证时使用ArgumentCaptor,而不是在打桩时。
  在打桩时使用ArgumentCaptor会降低测试代码的可读性,因为captor是在断言(又称为 验证/then)代码块之外创建的。
  另外,它可以降低本地化的缺点,因为如果测试桩函数没有被调用,那么参数就不会被捕获。

  总之,ArgumentCaptor与自定义的参数匹配器相关。
  这两种技术都能用于检测外部传递到Mock对象的参数。
  然而,使用ArgumentCaptor在以下的情况下更合适 :
    自定义参数匹配器不能被重用
    你仅需要断言参数值
 

16. 真实的局部模拟对象(mocks) (1.8版本之后)

  在 Mockito 1.8 之前,spy() 方法并不会产生真正的局部mocks,而这无疑会让一些开发者困惑。

    //你能用spy()方法创建局部模拟的对象:
    List list = spy(new LinkedList());

  在一些特殊的情况下局部mocks是方便的:处理不能轻易修改的代码(第三方接口,临时重构的遗留代码等)。
  在新的测试驱动和架构优秀的代码上,不要使用局部mocks的。

17. 重置mocks对象 (1.8版本之后)

  通常情况下你不会需要重设你的mocks,只需要为每个测试方法重新创建一个mocks就可以了。
  添加 reset() 方法的唯一原因是让它能与容器注入的mocks协作。

    List mock = mock(List.class);
    when(mock.size()).thenReturn(10);
    mock.add(1);

    reset(mock);
    //在此刻,mock对象遗忘了所有的interactions & stubbing

18. 故障排查与验证框架的使用 (1.8版本之后)

  FAQ: https://github.com/mockito/mockito/wiki/FAQ
  mailing list: https://groups.google.com/group/mockito

  validateLockitoUsage()显式验证框架状态,以检测Mockito的无效使用。
  https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#validateMockitoUsage()

19.行为驱动开发的别名 (1.8版本之后)

  用行为驱动开发的风格来写测试用例,会使用//given //when //then 注解作为测试方法的基础部分。
  BDDMockito 类介绍了一个别名,使你的测试桩方法调用 BDDMockito.given(Object) 方法。

    import static org.mockito.BDDMockito.*;
    
    Seller seller = mock(Seller.class);
    Shop shop = new Shop(seller);
    
    public void shouldBuyBread() throws Exception {
      //given
      given(seller.askForBread()).willReturn(new Bread());
    
      //when
      Goods goods = shop.buyBread();
    
      //then
      assertThat(goods, containBread());
    }

20. 可序列化的mocks(1.8.1版本之后)

  模拟的对象是可以被序列化的。有了这个特性,你就可以在要求依赖项是可序列化的地方,使用模拟对象。
  警告:这个特性应该在单元测试中少用。

  用MockSettings.serializable():创建一个可序列化的mock对象:

    List serializableMock = mock(List.class, withSettings().serializable());

  spy()方法没有MockSettings的参数,可以用下面的方法创建spy对象

    List<Object> list = new ArrayList<Object>();
    List<Object> spy = mock(ArrayList.class, withSettings()
                 .spiedInstance(list)
                 .defaultAnswer(CALLS_REAL_METHODS)
                 .serializable());

21. 新的注解 : @Captor,@Spy,@InjectMocks (1.8.3版本之后)

  @Captor 简化 ArgumentCaptor 的创建 - 当需要捕获的参数是一个令人讨厌的泛型类,而且你想避免编译时警告。
  @Spy - 你可以用它代替 spy(Object) 方法
  @InjectMocks - 自动将模拟或监视的对象注入到被测试对象中。

  需要注意的是 @InjectMocks 也能与 @Spy 一起使用,这就意味着 Mockito会在测试中,将mocks对象注入局部mock对象中。这变得很复杂,所以你还是应该少用局部mock。

  所有新的注解都是只在MockitoAnnotations.initMocks(Object)被处理。
  就像@Mock注解,你能用内置runner: MockitoJUnitRunner 或 规则: MockitoRule来开启。 
 

22. 带超时的验证 (1.8.5版本之后)

  这个特性应该少被使用 - 找到更好的方法去测试多线程系统。
  还没有实现和 InOrder 验证协作。

    //在100毫秒内调用someMethod时通过
    //校验通过时会马上结束(可能不会等到100ms)
    verify(mock, timeout(100)).someMethod();
    //上一行例子是下面这行代码的别名:
    verify(mock, timeout(100).times(1)).someMethod();

    //someMethod()在100ms内执行了2次就会马上通过
    verify(mock, timeout(100).times(2)).someMethod();

    //等效: someMethod()在100ms内执行了2次就会马上通过
    verify(mock, timeout(100).atLeast(2)).someMethod();

23. 自动实例化被@Spy, @InjectMocks注解的字段以及构造函数注入 (1.9.0版本之后)

  Mockito 现在会通过构造器注入、setter注入 或字段注入方式,尽可能初始化带有 @Spy 和 @InjectMocks 注解的字段。
  为了利用这个特性,你需要使用 MockitoAnnotations.initMocks(Object), MockitoJUnitRunner 或 MockitoRule。

    //旧的写法:
    @Spy BeerDrinker drinker = new BeerDrinker();
    //新的写法:
    @Spy BeerDrinker drinker;
    //@InjectMocks注解也可以这么用:
    @InjectMocks LocalPub;

24. 单行测试桩 (1.9.0版本之后)

  Mockito 现在允许在打桩时创建模拟对象。主要是,它允许通过一行代码来创建一个测试桩。这对保持代码的整洁很有用。

    Car boringStubbedCar = when(mock(Car.class).shiftGear()).thenThrow(EngineNotStarted.class).getMock();

25. 验证被忽略的测试桩 (1.9.0版本之后)

  Mockito 现在允许为了更好的验证而忽略已有的测试桩。
  与 verifyNoMoreInteractions() 方法或验证 inOrder() 方法组合使用,有时很有用。
  警告,ignoreStubs() 可能会导致 verifyNoMoreInteractions(ignoreStubs(...)) 方法的过度使用。
  谨记在心,Mockito 没有推荐在每一个测试中用 verifyNoMoreInteractions() 方法

    verify(mock).foo();
    verify(mockTwo).bar();
    
    //忽略所有已经打桩的方法:
    verifyNoMoreInvocations(ignoreStubs(mock, mockTwo));
    
    //创建将忽略打桩信息的InOrder
    InOrder inOrder = inOrder(ignoreStubs(mock, mockTwo));
    inOrder.verify(mock).foo();
    inOrder.verify(mockTwo).bar();
    inOrder.verifyNoMoreInteractions();

26. mock详情 (2.2.x中改进)

  Mockito 提供API来查看mock对象的详情。这些API对高级用户和mock框架整合者很有用。

    //识别特定的对象是一个mock对象或者是spy对象:
    Mockito.mockingDetails(someObject).isMock();
    Mockito.mockingDetails(someObject).isSpy();
   
    //获取mock对象的详情信息,包括它的类型或默认的answer:
    MockingDetails details = mockingDetails(mock);
    details.getMockCreationSettings().getTypeToMock();
    details.getMockCreationSettings().getDefaultAnswer();
   
    //获取mock对象的执行器(invocations)和打桩信息(stubbings):
    MockingDetails details = mockingDetails(mock);
    details.getInvocations();
    details.getStubbings();
   
    //打印所有的交互(including stubbing, unused stubs)
    System.out.println(mockingDetails(mock).printInvocations());

27. 真实实例的委托调用 (Since 1.9.5)

  对于使用通常的spy API难以模拟或监控的对象有用。
  从 Mockito 的 1.10.11 版本开始, delegate 有可能和 mock 的类型相同也可能不同。
  如果不是同一类型,delegate 类型需要提供一个匹配方法否则就会抛出一个异常。
  
  可能用到这个特性的案例:
    带有 interface 的 final 类
    已经自定义代理的对象
    带有 finalize 方法的特殊对象,也就是避免执行2次

  和常规 spy 的不同:
    标准的 spy (spy(Object)) 包含被监视实例的所有状态信息,方法在 spy 对象上被调用。
    被监视的对象只在 mock 创建时被用来拷贝状态信息。
    如果你通过标准 spy 调用一个方法,这个 spy 会调用其内部的其他方法记录这次操作, 以便后面验证使用。等效于存根 (stubbed)操作。

    代理方式的 mock 只是简单的把所有方法委托给 delegate。
    delegate 一直被当成它所代理的方法使用。
    如果你调用委托的mock对象上的方法,它会调用其内部的其他方法,这些调用不会被记录,打桩动作 在这里也不会生效。
    委托方式的Mock 相对于标准的 spy 来说功能弱了很多,不过在标准 spy 不能被创建的时候这还是很有用。

  final class DontYouDareToMockMe implements list { ... }
  DontYouDareToMockMe awesomeList = new DontYouDareToMockMe();
  List mock = mock(List.class, AdditionalAnswers.delegatesTo(awesomeList));

  //这样不行,因为会调用真实方法,会抛出IndexOutOfBoundsException
  when(listWithDelegate.get(0)).thenReturn("foo");
  //要用doReturn打桩
  doReturn("foo").when(listWithDelegate).get(0);

28. MockMaker API (Since 1.9.5)

  为了满足用谷歌Android用户的需求,Mockito 现在提供一个扩展点,允许替换代理生成引擎。默认情况下,Mockito 使用 Byte Buddy 创建动态代理。
  这个扩展点是为想要扩展 Mockito 功能的高级用户准备的。比如,我们现在就可以在 dexmaker 的帮助下使用 Mockito 测试 Android。
  原因和示例请看 MockMaker 的文档

29. BDD 风格的验证 (Since 1.10.0)

  开始验证时,使用then关键字可以开启 Behavior Driven Development (BDD) 风格的验证。

    given(dog.bark()).willReturn(2);
    // when
    ...
    then(person).should(times(2)).ride(bike);

  更多信息请查阅 BDDMockito.then(Object)

30. 监视 或 模拟 抽象类 (1.10.12版本加入,在2.7.13 和 2.7.14版特征得到增强))

  现在可以方便的 spy 一个抽象类。注意,过度使用 spy 或许意味着代码的设计上有问题。
  目前的话只支持无参构造函数。

    //方便的API,新增重载的spy()方法:
    SomeAbstract spy = spy(SomeAbstract.class);
    
    //模拟抽象方法,监视接口默认方法(只有在2.7.13后才可用)
    Function function = spy(Function.class);
    
    //健壮的 API,通过配置构造器:
    OtherAbstract spy = mock(OtherAbstract.class, withSettings()
       .useConstructor().defaultAnswer(CALLS_REAL_METHODS));
    
    //模拟构造器带参数的抽象类(只有在2.7.14后才可用)
    SomeAbstract spy = mock(SomeAbstract.class, withSettings()
      .useConstructor("arg1", 123).defaultAnswer(CALLS_REAL_METHODS));
    
    //模拟非静态内部抽象类:
    InnerAbstract spy = mock(InnerAbstract.class, withSettings()
       .useConstructor().outerInstance(outerInstance).defaultAnswer(CALLS_REAL_METHODS));

31. Mockito的模拟对象 可以通过 classloaders 序列化/反序列化 (Since 1.10.0)

  Mockito 通过 classloader 引入序列化。和其他形式的序列化一样,所有 mock 层的对象类型都要可序列化, 包括 answers。
  因为序列化模式需要大量的工作,所以这是一个可选配置。

    // 常规的 serialization
    mock(Book.class, withSettings().serializable());
    // 通过 classloaders 序列化
    mock(Book.class, withSettings().serializable(ACROSS_CLASSLOADERS));

32. Deep stubs 更好的泛型支持 (Since 1.10.0)

  深度打桩模式 现在可以更好的查找类的泛型信息。这就意味着像这样的类 不必去 mock 它的行为就可以使用。

    class Lines extends List<Line> {
        // ...
    }
    lines = mock(Lines.class, RETURNS_DEEP_STUBS);
    
    //现在 Mockito 知道这是Line类型,而不是Object
    Line line = lines.iterator().next();

  请注意,大多数情况下 mock 返回一个 mock 对象是错误的。

33. Mockito JUnit rule (Since 1.10.17)

  Mockito 现在提供一个 JUnit 的 rule。
  目前为止,有两种方法可以初始化 fields ,这些fields使用了 Mockito 提供的注解比如 @Mock, @Spy, @InjectMocks 等等。
    用 @RunWith(@MockitoJUnitRunner.class) 标注 JUnit 测试类
    在 @Before 方法中调用 MockitoAnnotations.initMocks(Object)
  现在你可以选择使用一个 rule:

    @RunWith(YetAnotherRunner.class)
    public class TheTest {
        @Rule public MockitoRule mockito = MockitoJUnit.rule();
        // ...
    }

34. 开启和关闭插件的开关 (Since 1.10.15)

  这是一个测试特性,可以控制一个 mockito-plugin 开启或者关闭。
  详情请查看 PluginSwitch

35. 自定义验证失败信息 (Since 2.1.0)

  允许声明一个在验证失败时输出的自定义消息 示例:

    // 在验证失败时,会打印自定义的消息
    verify(mock, description("This will print on failure")).someMethod();
    // 任何验证模式下都能使用这种方式
    verify(mock, times(2).description("someMethod should be called twice")).someMethod();

36. Java 8 Lambda匹配器的支持 (Since 2.1.0)

  你可以在参数匹配器(ArgumentMatcher)上使用Java 8 lambda表达式,来减少对参数捕获器(ArgumentCaptor)的依赖。
  如果你需要验证mock对象上方法调用的输入是正确的,那么你需要正常使用参数捕获器来找到使用过的操作数,并且之后对它们做断言。
  写一个lambda来表示匹配关系是很容易的。你方法的参数使用argThat进行连接时,参数将作为强类型对象传给参数匹配器,所以通过这种方式能做任何事情。

    // 验证一个list只添加了某些长度的字符串(字符串长度小于5)
    // 注意 - 这种写法只有在Java 8下能编译通过
    verify(list, times(2)).add(argThat(string -> string.length() < 5));
    
    // Java 8 下,更复杂的例子 - 你可以通过函数式指定复杂的验证行为
    verify(target, times(1)).receiveComplexObject(argThat(obj -> obj.getSubObject().get(0).equals("expected")));
    
    // lambda的方式也可以被用到:定义不同入参下,mock对象的行为
    // in this case if the input list was fewer than 3 items the mock returns null
    when(mock.someMethod(argThat(list -> list.size()<3))).thenReturn(null);

37. Java 8 自定义Answer的支持 (Since 2.1.0)

  Answer接口只有一个方法,Java 8 使用lambda表达式来实现它非常简单。

    // answer每次都返回12
    doAnswer(invocation -> 12).when(mock).doSomething();

    // 用参数里的一个值作为返回值 - 转换成你想要的正确类型 
    doAnswer(invocation -> ((String)invocation.getArgument(1)).length())
      .when(mock).doSomething(anyString(), anyString(), anyString());

  可以定义answer/actions,用被调用方法的参数作为answer/actions中lambda的入参。
  它们依赖的相关answer接口在org.mockito.stubbing包下,Answer接口最多支持5个参数。

    // Java 8 - style 1
    doAnswer(AdditionalAnswers.answerVoid((operand, callback) -> callback.receive("dummy"))
        .when(mock).execute(anyString(), any(Callback.class));
   
    // Java 8 - style 2 - 假设AdditionAnswers已经静态导入
    doAnswer(answerVoid((String operand, Callback callback) -> callback.receive("dummy"))
        .when(mock).execute(anyString(), any(Callback.class));
   
    // Java 8 - style 3 - 被模拟的方法是测试类的静态成员
    private static void dummyCallbackImpl(String operation, Callback callback) {
        callback.receive("dummy");
    }
   
    doAnswer(answerVoid(TestClass::dummyCallbackImpl)
        .when(mock).execute(anyString(), any(Callback.class));

38. 元数据和泛型信息保留 (Since 2.1.0)

  Mockito 现在会保留mock方法和类上的注解信息,也会保留泛型的元信息。

    @MyAnnotation
    class Foo {
      List<String> bar() { ... }
    }
    
    Class<?> mockType = mock(Foo.class).getClass();
    assert mockType.isAnnotationPresent(MyAnnotation.class);
    assert mockType.getDeclaredMethod("bar").getGenericReturnType() instanceof ParameterizedType;

  使用 Java 8,Mockito 现在也保存类型注解(type annotations)。
  这是默认行为,如果有可选择的MockMaker被使用,这种行为可能不会持续。

39. 模拟final类型,枚举 和 final方法 (Since 2.1.0)

  Mockito 现在对模拟final类和方法提供一个孵化中的,可选的的支持。
  这个可选的mock maker,它使用Java instrumentation API和子类两者结合的方式,而不是创建一个新的类来代表这个mock对象。通过这种方式,使模拟final类和方法成为了可能。
  这个mock maker默认是关闭的,能通过mockito的扩展机制来被激活,只要在classpath上创建一个文件/mockito-extensions/org.mockito.plugins.MockMaker,文件包含mock-maker-inline这个值。

  方便起见,Mockito团队提供了一个artifact,这个artifact里mock maker已经配置好。在项目中引入mockito-inline artifact来代替mockito-core artifact。
  注意,等到对final类和方法的模拟功能集成到默认的mock maker后,这个artifact可能会停止。

  关于这个mock maker几个需要注意的点是:
    模拟final类和枚举的mock配置是不兼容的:
      显示的序列化支持: withSettings().serializable()
      特殊接口: withSettings().extraInterfaces()
    一些方法不能被模拟:
      java.*下的包可见方法
      本地方法
    这个mock maker被设计成围绕着Java Agent运行时的附件(attachment);这个要求一个兼容的JVM
    当在Java 9之前的非JDK虚拟机上运行时,可以在启动JVM时使用-javaagent参数手动添加Byte Buddy Java代理jar。
 

40. “严格的”Mocktio能提高生产效率并使测试用例更清晰(2.+版本之后)

  通过JUnit4 Rules来开启严格打桩 - MockitoRule.strictness(Strictness) with Strictness.STRICT_STUBS
  通过JUnit4 Runner来开启严格打桩 - MockitoJUnitRunner.StrictStubs
  通过JUnit5 Extension来开启严格打桩 - org.mockito.junit.jupiter.MockitoExtension
  通过TestNG Listener来开启严格打桩 - MockitoTestNGListener
  如果你不能使用runner/rule来开启严格打桩 - MockitoSession
  通过MockitoJUnitRunner发现非必要测试桩
  打桩参数不匹配的警告,记录在MockitoHint中

  Mockito默认是一个“宽松的”模拟框架。有时让Mockito的测试用例难于被debug。
  从2.1版本开始,Mockito拥有一些新的特性,这些特性推动框架走向“严格性”。
  我们想让Mockito提供出色的调试能力,同时也不丢失它核心的模拟风格,针对直观性,清晰性和整洁的测试代码进行优化。
 

41. 框架集成的高级公开API (2.10.+版本之后)

  它旨在用于需要用一些定制逻辑扩展或包装Mockito的其他测试工具和模拟框架。
    新的 MockitoPlugins - 使框架集成者能够访问默认的Mockito插件。当需要实现自定义插件(例如MockMaker),并且将某些行为委托给默认Mockito的实现。
    新的 MockSettings.build(Class) - 创建模拟配置的不可变的视图,供Mockito稍后使用。使用InvocationFactory创建invocations 或 当实现自定义MockHandler时,视图将非常有用。
    新的 MockingDetails.getMockHandler() - 其他框架可能使用这个mock处理器,以编程方式对mock对象模拟调用。
    新的 MockHandler.getMockSettings() - 用于获取创建模拟对象的配置。
    新的 InvocationFactory - 提供创建调用对象实例的方法。有益于需要以编程方式模拟在mock对象上的方法调用的框架集成。
    新的 MockHandler.getInvocationContainer() - 提供获取没有方法的调用容器对象(makrer接口)。容器需要隐藏它内部的实现机制,并且避免将其泄露给公共API。
    改变Stubbing接口 - Stubbing现在继承自 Answer 接口。它是向后兼容的,因为Stubbing接口不是一个可扩展的(看@NotExtensible注解)。这个改变对用户来说是无感的。
    NotExtensible - 公共的注解,指示用户不应该对提供给定类型的自定义实现。帮助框架集成者和我们的用户理解怎么去安全的使用Mockito API。
 

42. 用于集成的新API: 监听验证开始(verification start)事件(2.11.+版本之后)

  新的 VerificationStartedListener 和 VerificationStartedEvent 使框架集成者替换模拟对象以进行验证。主要的驱动用例是Spring Boot的集成。
  新的公共方法MockSettings.verificationStartedListeners(VerificationStartedListener...)允许在mock创建时,提供验证启动的监听器。
  新的方便的方法MockingDetails.getMock()被添加,用来使MockingDetails API更加完整。
 

43. 用于集成的新API: 测试框架支持MockitoSession(2.15.+版本之后)

  MockitoSessionBuilder and MockitoSession得到增强,以便通过测试框架集成实现重用(例如MockitoRule对于JUnit一样)。
    MockitoSessionBuilder.initMocks(Object...)
      允许传入多个测试类的实例,用来初始化被Mockito注解(例如@Mock)注释的字段。
      当测试使用多个(例如内嵌的)测试类实例时,这个方法对高级框架集成非常有用(例如 JUnit Jupiter)。
    MockitoSessionBuilder.name(String)
      允许将名称从测试框架传递到MockitoSession,当使用Strictness.WARN时,MockitoSession将用于打印警告。
    MockitoSessionBuilder.logger(MockitoSessionLogger)
      使定制,用于完成模拟时产生的提示/告警 的logger成为了可能(对于测试和连接JUnit Jupiter等测试框架提供的报告功能很有用)。
    MockitoSession.setStrictness(Strictness)
      允许针对一次性场景改变MockitoSession的严格性,
      例如,它可以为一个类下的所有测试用例设置一个默认的严格性,但是也可以改变单个或几个测试用例的严格性。
    MockitoSession.finishMocking(Throwable)
      避免由于存在多个相互竞争的失败而可能出现的混淆。
      当提供的失败用例不是null时,它会关闭某些检查。
 

44. org.mockito.plugins.InstantiatorProvider泄露内部API所以被org.mockito.plugins.InstantiatorProvider2替代
 

45. 新的JUnit Jupiter (JUnit5+) extension

  想要和JUnit Jupiter(JUnit5+)集成,可以使用org.mockito:mockito-junit-jupiter artifact。
  请查阅MockitoExtension的Java文档。

46. 新的Mockito.lenient()和MockSettings.lenient()方法(2.20.0版本之后)

  在Mockito2早期开始,就有严格打桩的特性。它非常有用,因为它驱动更整洁的测试用例和提供生产率。
  严格打桩上报非必要的桩点,检查打桩参数不匹配 并且使测试用例更DRY(Strictness.STRICT_STUBS)。
  
  在某些情况,你可能从严格打桩得到错误的否定。
  你可以将特定打桩配置为宽松的,而所有其他的打桩和mocks使用严格打桩:

    lenient().when(mock.foo()).thenReturn("ok");

  如果你想在给定的mock对象的所有打桩都是宽松的,你可以按照下面的方式配置mock对象:

    Foo mock = Mockito.mock(Foo.class, withSettings().lenient());

47. 用于在内联mocking中清除mock状态的新API(2.25.0版本之后)

  在某些特定的、罕见的场景中,内联mocking会导致内存泄漏。没有干净的方法可以完全缓解这个问题。
  因此,我们引入了一个新的API来显式清除mock状态(只有在内联mocking中才有意义!)。

    public class ExampleTest {
        @After
        public void clearMocks() {
            Mockito.framework().clearInlineMocks();
        }
        @Test
        public void someTest() {
            ...
        }
    }

48. 模拟静态方法(3.4.0版本之后)

  当使用内联mock maker时,可以在当前线程和用户定义的范围内模拟静态方法调用。
  通过这种方式,Mockito可以确保并发和顺序运行的测试不会产生干扰。
  为了确保静态mock保持临时性,建议在try-with-resources构造中定义作用域。
  在下面的例子中,除非被模拟,否则Foo类型的静态方法将返回Foo:

    assertEquals("foo", Foo.method());
    try (MockedStatic mocked = mockStatic(Foo.class)) {
      mocked.when(Foo::method).thenReturn("bar");
      assertEquals("bar", Foo.method());
      mocked.verify(Foo::method);
    }
    assertEquals("foo", Foo.method());

  由于静态mock定义了作用域,因此一旦释放作用域,它就会返回到原来的行为。要定义mock行为并验证静态方法调用,请使用返回的MockedStatic。
 

49. 模拟对象构造(3.5.0版本之后)

  当使用内联mock maker时,可以在当前线程和用户定义的范围内对构造函数调用生成mock。
  通过这种方式,Mockito可以确保并发和顺序运行的测试不会产生干扰。
  为了确保构造函数mock保持临时性,建议在try-with-resources构造中定义作用域。
  在以下示例中,Foo类型的构造将生成一个mock:

    assertEquals("foo", new Foo().method());
    try (MockedConstruction mocked = mockConstruction(Foo.class)) {
      Foo foo = new Foo();
      when(foo.method()).thenReturn("bar");
      assertEquals("bar", foo.method());
      verify(foo).method();
    }
    assertEquals("foo", new Foo().method());

  由于模拟构造的定义范围,一旦释放范围,对象构造就会返回到其原始行为。要定义mock行为并验证方法调用,请使用返回的MockedConstruction。
 

50. 仅模拟接口时避免代码生成(3.12.2版本之后)

  JVM提供了代理功能,用于创建接口类型的动态代理。
  对于大多数应用程序,Mockito必须能够模拟默认mock maker所支持的类,甚至能够模拟内联mock maker支持的最终类。
  为了创建这样的mock,Mockito需要设置不同的JVM设施,并且必须应用代码生成。

  如果只模拟接口,则可以选择使用基于代理API的org.mockito.internal.creation.proxy.ProxyMockMaker,这避免了其他模拟生成器的不同开销,但也限制了对接口的模拟。
  这个mock-maker可以通过mockito扩展机制显式激活,只需在类路径中创建一个文件/mmockito-extensions/org.mockito.plugins.MockMaker,其中包含mock-maker-proxy值。
 

51. 把类标记为不可模拟(4.1.0版本之后)

  可以使用@DoNotMock注解标记不能模拟的类或接口。

52. @Mock注解和MockSettings.strictness()方法的新strictness属性(4.6.0版本之后)

  你可以将特定模拟配置为宽松的,而所有其他的模拟使用严格的:

    @Mock(strictness = Strictness.LENIENT)
    Foo mock;
    // using MockSettings.withSettings()
    Foo mock = Mockito.mock(Foo.class, withSettings().strictness(Strictness.WARN));

53. 指定单个mock的mock maker(4.8.0版本之后)

   // using annotation
   @Mock(mockMaker = MockMakers.SUBCLASS)
   Foo mock;
   // using MockSettings.withSettings()
   Foo mock = Mockito.mock(Foo.class, withSettings().mockMaker(MockMakers.SUBCLASS));

54. mock/spy不指定类(4.9.0版本之后)

  Mockito将自动检测所需的类。

    Foo foo = Mockito.mock();
    Bar bar = Mockito.spy();

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
注:下文中的 *** 代表文件名中的组件名称。 # 包含: 中文-英文对照文档:【***-javadoc-API文档-中文(简体)-英语-对照版.zip】 jar包下载地址:【***.jar下载地址(官方地址+国内镜像地址).txt】 Maven依赖:【***.jar Maven依赖信息(可用于项目pom.xml).txt】 Gradle依赖:【***.jar Gradle依赖信息(可用于项目build.gradle).txt】 源代码下载地址:【***-sources.jar下载地址(官方地址+国内镜像地址).txt】 # 本文件关键字: 中文-英文对照文档,中英对照文档,java,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,中文API文档,手册,开发手册,使用手册,参考手册 # 使用方法: 解压 【***.jar中文文档.zip】,再解压其中的 【***-javadoc-API文档-中文(简体)版.zip】,双击 【index.html】 文件,即可用浏览器打开、进行查看。 # 特殊说明: ·本文档为人性化翻译,精心制作,请放心使用。 ·本文档为双语同时展示,一行原文、一行译文,可逐行对照,避免了原文/译文来回切换的麻烦; ·有原文可参照,不再担心翻译偏差误导; ·边学技术、边学英语。 ·只翻译了该翻译的内容,如:注释、说明、描述、用法讲解 等; ·不该翻译的内容保持原样,如:类名、方法名、包名、类型、关键字、代码 等。 # 温馨提示: (1)为了防止解压后路径太长导致浏览器无法打开,推荐在解压时选择“解压到当前文件夹”(放心,自带文件夹,文件不会散落一地); (2)有时,一套Java组件会有多个jar,所以在下载前,请仔细阅读本篇描述,以确保这就是你需要的文件;

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值