Mockito3.x详解

2024-01-09

目录

Mockito

Mockito 是一个针对 Java 的单元测试模拟框架,它与 EasyMock 和 jMock 很相似,都是为了简化单元测试过程中测试上下文 ( 或者称之为测试驱动函数以及桩函数 ) 的搭建而开发的工具

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

Maven依赖:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>3.6.28</version>
    <scope>test</scope>
</dependency>

1、简单示例

Mockito的使用方式与EasyMock不同,不需要提前录制行为。一旦mock对象被创建了,mock对象会记住所有的交互动作。然后就可以选择性的验证你感兴趣的交互动作。

import org.junit.Test;
import java.util.List;
import static org.mockito.Mockito.*;

public class MockitoTest {

    @Test
    public void test() {
        //创建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。

注意:打桩动作可以被覆写,多次使用同一参数对同一函数打桩时,调用时返回最后一次打桩的值。一旦测试桩函数被调用,该函数将会一直返回固定的值。

@Test
public void testStub() {
    //可以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、参数匹配器

Mockito 以自然的 java 风格验证参数值:通过使用equals()方法。

内置常用匹配器位于 org.mockito.ArgumentMatchers 类中:

  • 匹配所有东西,包括null和可变参数
    • any()
  • 匹配给定类型的对象,不包括null :
    • any(Class<T> type)
  • 实现给定类的对象参数 :
    • isA​(Class<T> type)
  • 匹配基本数据类型或者非null 的基本数据类型对应的包装类或字符串 :
    • anyBoolean()
    • anyByte()
    • anyChar()
    • anyInt()
    • anyLong()
    • anyFloat()
    • anyDouble()
    • anyShort()
    • anyString()
  • 匹配集合类 :
    • anyList()
    • anySet()
    • anyMap()
    • anyCollection()
    • anyIterable()
  • 相等匹配
    • eq(boolean)
    • eq(byte)
    • eq(char)
    • eq(double)
    • eq(float)
    • eq(int)
    • eq(long)
    • eq(short)
    • eq(T)
  • 相同引用匹配
    • same(T)
  • 反射后与给定的对象参数一致,支持从类中排除给定字段 :
    • refEq(T, String...)
  • Null判断 :
    • isNull()
    • notNull()
    • isNull(Class<T> type)
    • notNull(Class<T> type)
    • isNotNull()
    • isNotNull(Class<T> type)
  • 匹配null或者给定的类型 :
    • nullable(Class<T> clazz)
  • 字符串匹配 :
    • contains(String)
    • matches(String)
    • matches(Pattern)
    • endsWith(String)
    • startsWith(String)
  • 使用用户自定义参数匹配器 :
    • 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)

附加匹配器位于 org.mockito.AdditionalMatchers 类:

  • 逻辑组合匹配器
    • and() :逻辑与
    • or :逻辑或
    • not() :逻辑非
  • 比较匹配器
    • geq() :大于等于
    • gt() :大于
    • leq() :小于等于
    • lt() :小于
  • 数组相等匹配器 :
    • aryEq()
  • 绝对值差小于某个值判定浮点数相等 :
    • eq(double value, double delta)
    • eq(float value, float delta)
  • 使用compareTo比较相等 :
    • cmpEq(T value)
  • 匹配器返回值包含指定字符串 :
    • find(String regex)
@Test
public void testMatcher() {
    LinkedList mockedList = mock(LinkedList.class);

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

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

    //参数匹配器也能用Java 8 Lambda风格编写
    when(mockedList.add(argThat(someString -> ((String)someString).length() > 5))).thenReturn(true);

    //调用,输出element
    System.out.println(mockedList.get(999));

    //调用,输出true
    System.out.println(mockedList.add("hello world"));

    //可以使用参数匹配器验证
    verify(mockedList).get(anyInt());
}

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

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

4、调用次数验证

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

@Test
public void testTimes() {
    List mockedList = mock(LinkedList.class);

    //调用一次
    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("once");//至少一次
    verify(mockedList, atLeast(2)).add("twice");//至少两次
    verify(mockedList, atMost(5)).add("three times");//至多5次
}

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

@Test
public void testVoidThrow() {
    List mockedList = mock(LinkedList.class);

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

6、验证调用顺序

验证单个mock对象的调用顺序:

@Test
public void testSingleObjectOrder() {
    //创建单个mock对象
    List singleMock = mock(LinkedList.class);

    //顺序调用
    singleMock.add("first");
    singleMock.add("second");

    //创建InOrder对象,传递被验证的mock对象
    InOrder inOrder = inOrder(singleMock);

    //验证
    inOrder.verify(singleMock).add("first");
    inOrder.verify(singleMock).add("second");
}

验证多个mock对象的调用顺序:

@Test
public void testMultiObjectOrder() {
    //创建多个mock对象
    List aList = mock(LinkedList.class);
    List bList = mock(LinkedList.class);

    //调用
    aList.add("first");
    bList.add("second");

    //创建InOrder对象,传递被验证的mock对象
    InOrder inOrder = inOrder(aList, bList);

    //验证
    inOrder.verify(aList).add("first");
    inOrder.verify(bList).add("second");
}

按顺序验证很灵活,不必一一验证所有交互,而只需按顺序验证您有兴趣测试的交互。

7、验证从未发生过的交互

@Test
public void testInteraction() {
    List aList = mock(LinkedList.class);
    List bList = mock(LinkedList.class);


    aList.add("one");
    //验证add("one")
    verify(aList).add("one");
    //验证从未调用过add("two")方法
    verify(aList, never()).add("two");
    //验证bList从未有过交互
    verifyNoInteractions(bList);
}

8、验证冗余调用

@Test
public void testNoMoreInteractions() {
    List mockedList = mock(LinkedList.class);

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

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

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

  • 最大限度地减少重复的模拟创建代码。
  • 使测试类更具可读性。
  • 使验证错误更容易阅读,因为字段名称 用于标识模拟。
//开启@Mock注解的注入功能
@RunWith(MockitoJUnitRunner.class)
public class MockitoTest2 {

    @Mock
    private List list;
    @Mock
    private Map map;

    @Test
    public void test() {
        list.add("a");
        map.put("key", "value");

        verify(list).add("a");
        verify(map).put("key", "value");
    }

}

如果不使用 @RunWith(MockitoJUnitRunner.class) ,那么需要在运行测试函数之前调用下面这句代码:

MockitoAnnotations.initMocks(MockitoTest2.class);

10、同一目标多次打桩,返回不同的值(迭代器)

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

@Test
public void testIt() {
    Iterator mockIt = mock(Iterator.class);
    //对next()方法多次打桩
    when(mockIt.next())
            .thenReturn("a")
            .thenReturn("b")
            .thenThrow(new RuntimeException());

    //第一次调用输出a
    System.out.println(mockIt.next());
    //第二次调用输出b
    System.out.println(mockIt.next());
    //第三次调用抛出异常
    mockIt.next();
}

也可以简写:

when(mockIt.next())
        .thenReturn("a", "b");

11、使用回调方式打桩

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

@Test
public void testAnswer() {
    List mockList = mock(List.class);

    when(mockList.add("one")).thenAnswer(
            new Answer<Object>() {
                @Override
                public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
                    //参数列表
                    Object[] arguments = invocationOnMock.getArguments();
                    //mock对象
                    Object mock = invocationOnMock.getMock();
                    return arguments.length > 0 ? true : false;
                }
            }
    );

    System.out.println(mockList.add("one"));
}

12、对void返回值函数打桩

通过when(Object)为无返回值的函数打桩有不同的方法:

  • doReturn()
  • doThrow()
  • doAnswer()
  • doNothing()
  • doCallRealMethod()

12.1、doReturn

doReturn()在极少数无法使用的场合使用when(Object)。
请注意,when(Object)始终建议进行存根操作,因为它是参数类型安全且更具可读性(特别是在存根连续调用时)。

以下是 doReturn() 派上用场的罕见情况:

  • 当监视真实对象并调用间谍的真实方法时会带来副作用:
List list = new LinkedList();
List spy = spy(list);

//调用了real方法,所以spy.get(0)抛出IndexOutOfBoundsException(因为列表仍然为空)
when(spy.get(0)).thenReturn("foo");

//必须使用doReturn处理这种副作用
doReturn("foo").when(spy).get(0);
  • 覆盖之前的异常桩:
when(mock.foo()).thenThrow(new RuntimeException());

//调用方法,从而引发RuntimeException。
when(mock.foo()).thenReturn("bar");

//使用doReturn覆盖
doReturn("bar").when(mock).foo(); 

12.2、doThrow

doThrow()当您想要对带有异常的 void 方法进行存根时 使用。

doThrow(new RuntimeException()).when(mock).someVoidMethod();

12.3、doAnswer

doAnswer()当您想要使用 generic 存根 void 方法时使用Answer。

doAnswer(new Answer() {
   public Object answer(InvocationOnMock invocation) {
       Object[] args = invocation.getArguments();
       Mock mock = invocation.getMock();
       return null;
   }})
.when(mock).someMethod();

12.4、doNothing

用于doNothing()设置 void 方法不执行任何操作。请注意,模拟上的 void 方法默认不执行任何操作! 然而,在极少数情况下 doNothing() 会派上用场:

  • 对 void 方法进行连续调用存根:
doNothing().
   doThrow(new RuntimeException())
   .when(mock).someVoidMethod();

   mock.someVoidMethod();
   mock.someVoidMethod();
  • 当您监视真实对象并且希望 void 方法不执行任何操作时:
List list = new LinkedList();
List spy = spy(list);

doNothing().when(spy).clear();

spy.add("one");
spy.clear();

12.5、doCallRealMethod

doCallRealMethod()当您想要调用方法的实际实现时 使用。

Foo mock = mock(Foo.class);
doCallRealMethod().when(mock).someVoidMethod();

// this will call the real implementation of Foo.someVoidMethod()
mock.someVoidMethod();

13、监控真实对象

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

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

@Test
public void testSpy() {
    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()函数族来进行打桩。

@Test
public void testSpy2() {
    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);

    System.out.println(spy.get(0));
}

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

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

14、修改没有测试桩调用的默认返回值(since 1.7)

你可以创建mock对象,用指定策略来作为它的返回值。

@Test
public void testDefault() {
    List mockList = mock(List.class, RETURNS_DEFAULTS);
    
    //不打桩,使用默认返回策略

    //调用
    System.out.println(mockList.get(0));//null
    System.out.println(mockList.size());//0
    System.out.println(mockList.isEmpty());//false

    //也可以自定义默认策略
    List mockList2 = mockList(List.class, new Answer<>(){
        @Override
        public Object answer(InvocationOnMock invocation) throws Throwable {
            return null;
        }
    });
}

内置的默认策略如下:

  • 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");
  • CALLS_REAL_METHODS :调用实现类的真实方法
  • RETURNS_SELF :每当调用的方法返回的Type等于类或超类时,允许Builder mock返回自身。

15、为下一步的断言捕获参数(since 1.8)

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

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

使用场景:

  • 自定义参数匹配器不能被重用
  • 仅需要断言参数值

16、真实的局部模拟对象(mocks) (since 1.8)

在 Mockito 1.8 之前,spy() 方法并不会产生真正的局部mocks:

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

17、重置mocks对象 (since 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、故障排除和验证框架使用情况

  • 如果遇到任何问题,我鼓励您阅读 Mockito 常见问题解答: https://github.com/mockito/mockito/wiki/FAQ
  • 如有疑问,您也可以发布到mockito邮件列表: http://groups.google.com/group/mockito

19、行为驱动开发的别名(since 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(since 1.8.1)

模拟的对象是可以被序列化的。有了这个特性,你就可以在要求依赖项是可序列化的地方,使用模拟对象。

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(since 1.8.3)

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

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

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

22、带超时的验证 (since 1.8.5)

//在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注解的字段以及构造函数注入 (since 1.9.0)

Mockito 现在会通过构造器注入、setter注入 或字段注入方式,尽可能初始化带有 @Spy 和 @InjectMocks 注解的字段。

为了利用这个特性,你需要使用 MockitoAnnotations.initMocks(Object), MockitoJUnitRunner 或 MockitoRule。

  • @Spy :在Mockito中用于创建spy对象,使用方法如下
@RunWith(MockitoJUnitRunner.class)
public class MockitoTest3 {

    @Spy//旧写法
    private List old = new LinkedList();

    //新写法,反射调用无参构造
    @Spy//不能实例化内部类、本地类、抽象类和接口
    private List list;

    @Test
    public void test() {
        when(list.size()).thenReturn(100);

        System.out.println(list.size());
    }
}
  • @InjectMocks :这是一个注入mock对象的操作
@Spy
private ClassName mockedObject;

@InjectMock
private TestedClass TestedObj = new TestedClass();

@InjectMock下面声明了一个待测试的对象,若该对象有类型为ClassName的成员变量,@Spy定义的mock对象将会被注入到这个待测试的对象中,既TestedObj的类型为ClassName的成员被直接赋值为mockedObject。

24、单行测试桩 (since 1.9.0)

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

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

25、验证被忽略的测试桩 (since 1.9.0)

Mockito 现在允许为了更好的验证而忽略已有的测试桩。
与 verifyNoMoreInteractions() 方法或验证 inOrder() 方法组合使用,有时很有用。

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

30、监视 或 模拟 抽象类

现在可以方便的 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 模拟可以跨类加载器序列化/反序列化(since 1.10.0)

Mockito 引入了跨类加载器的序列化。与任何其他形式的序列化一样,模拟层次结构中的所有类型都必须可序列化,包括答案。由于这种序列化模式需要做更多的工作,因此这是一个选择加入的设置。

    // 常规的 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();

33、Mockito JUnit rule (Since 1.10.17)

  • 使用注释 JUnit 测试类@RunWith(MockitoJUnitRunner.class)
  • 在 @Before 方法中调用 MockitoAnnotations.initMocks(Object)

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)

    // 验证一个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;

39、mock静态方法(since 3.4.0)

依赖:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <version>3.6.28</version>
    <scope>test</scope>
</dependency>

被测试类:

public class StaticUtils {

    //无参方法
    public static String name() {
        return "hello world";
    }

    //有参方法
    public static List<Integer> range(int start, int end) {
        return IntStream.range(start, end)
                .boxed()
                .collect(Collectors.toList());
    }
}

测试类:

import org.junit.Test;
import org.mockito.MockedStatic;

import java.util.Arrays;

import static org.mockito.Mockito.*;
import static org.junit.Assert.*;

public class MockStaticTest {

    /**
     * 测试无参静态方法:
     * 1. 需要把我们的mockStatic写到try-with-resources代码块中,
     *    以确保我们在使用完mock的静态方法后能够及时释放;
     * 2. 同时我们会在测试用例的前后分别校验name方法的返回值,确保前后都没有被mock,
     *    返回的真实值,符合我们的预期;
     */
    @Test
    public void test1() {
        assertEquals(StaticUtils.name(), "hello world");

        try (MockedStatic<StaticUtils> mock = mockStatic(StaticUtils.class)) {
            mock.when(StaticUtils::name).thenReturn("aaa");
            assertEquals(StaticUtils.name(), "aaa");
        }

        assertEquals(StaticUtils.name(), "hello world");
    }

    /**
     * 测试有参静态方法:
     */
    @Test
    public void test2() {
        assertEquals(StaticUtils.range(2, 6), Arrays.asList(2, 3, 4, 5));

        try (MockedStatic<StaticUtils> mock = mockStatic(StaticUtils.class)) {
            mock.when(() -> StaticUtils.range(2, 6))
                    .thenReturn(Arrays.asList(1, 2, 3));
            assertEquals(StaticUtils.range(2, 6), Arrays.asList(1, 2, 3));
        }

        assertEquals(StaticUtils.range(2, 6), Arrays.asList(2, 3, 4, 5));
    }
}

40、mock对象构造(since 3.5.0)

可以在当前线程和用户定义的范围内对构造函数调用生成mock。通过这种方式,Mockito可以确保并发和顺序运行的测试不会产生干扰。

为了确保构造函数mock保持临时性,建议在try-with-resources构造中定义作用域。

    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());
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Mockito3.x详解 的相关文章

随机推荐

  • Python selenium实现断言3种方法解析

    1 if else 判断进行断言 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
  • INT201 形式语言与自动机笔记(下)

    L6 Context Free Languages 上下文无关语言 Context Free Grammar CFG 是一组用于生成字符串模式的递归规则 上下文无关的语法可以描述所有的常规语言 但它们不能描述所有可能的语言 e g 遵循这些
  • 研发实验室建设

    研发实验室是科技创新的核心场所 对于企业的技术研发和产品创新具有重要意义 为了提高研发实验室的效率和质量 建设一个符合要求的实验室是至关重要的 SICOLAB喜格 将介绍研发实验室建设的关键要素和注意事项 一 实验室规划与布局 1 空间规划
  • 软件测试开发/全日制/测试管理丨持续集成、持续交付、持续部署

    持续集成 持续交付 持续部署 是构建现代软件开发流程的三大支柱 这三个概念代表了现代软件开发中重要的实践 它们旨在通过自动化和频繁的发布流程来提高开发团队的效率和软件交付的可靠性 持续集成 CI 持续集成是指开发人员将代码频繁地集成到共享的
  • 智慧农业之新导物联RFID果园苗圃系统-新导物联

    新导物联RFID果园苗圃系统是一种利用射频识别技术 RFID 来管理果园和苗圃的系统 该系统通过在植物或容器上附加RFID标签 将每个植物或容器与唯一的识别代码相关联 使用RFID果园苗圃系统的主要目的是实现对植物和容器的自动化追踪和管理
  • 【数据压缩】基于哈夫曼编码的数据压缩算法matlab,输出哈夫曼编码树

    目录 1 算法仿真效果 2 MATLAB源码 3 算法概述 3 1 哈夫曼树构建 3 2 哈夫曼编码生成
  • 职场生存能力最强的5类人:如何成为职场中的佼佼者?

    职场生存能力最强的5类人 如何成为职场中的佼佼者 在职场中 生存能力强的人往往更容易获得成功 他们具备各种能力和特质 使他们能够在激烈的竞争中脱颖而出 本文将介绍职场生存能力最强的5类人 看看你是否具备这些特点 一 适应能力强的人 在职场中
  • Jmeter扩展函数?年薪50W+的测试大佬教你怎么玩

    很多同学 都问我 老师 我的 jmeter 里面 怎么没有 MD5 函数 base64 函数也没有 我是不是用了假的 jmeter 哈哈哈 不是的 jmeter 的函数 有自带函数和扩展函数两大块 自带函数 就是 jmeter 官方自带的
  • 计算机丢失mfc140.dll怎么办?解决mfc140.dll缺失的3种方法分享

    计算机丢失mfc140 dll怎么办 在使用微软办公软件的时候 可能会弹出一个错误提示框说 找不到mfc140 dll 无法继续执行代码 为了不影响工作效率 我们可能需要亲自动手尝试修复这一问题 以下是一些mfc140 dll缺失的3种方法
  • INT201 形式语言与自动机笔记(上)

    Lec1 Overview Alphabet and String 字母表与字符串 Alphabet 字母表 a finite nonempty set of symbols String word a finite sequence of
  • 如何处理不稳定的自动化测试?

    abluecolor 在解决这个问题之前 请停止编写更多测试 因为这将花费你较高的测试维护成本 你需要尽快行动起来对不稳定的原因进行深入研究 找到不稳定的根因 并且尝试在流程 环境和代码方面做一些优化工作解决它 MasterKindew 如
  • mybatisPlus 将List<String>字段转成json字符串,使用JacksonTypeHandler以及自定义类型处理器实现

    文章目录 场景 使用JacksonTypeHandler实现类型转换 自定义StringListTypeHandler处理器实现 场景 项目中经常需要将List转成json存储到配置文件中 mybatisPlus默认实现了JacksonTy
  • Stable Diffusion运行时自动重启的小程序

    Stable Diffusion在运行过程中 常常因为各种原因导致服务挂掉 所以希望能定时检查程序是否存活 如果dead则重启一下 思路 stable diffusion运行时需要使用GPU 如果GPU没有在用 并且GPU对应的进程也找不到
  • 多进程之Binder的意外死亡及权限校验

    Android多进程系列 Android 多进程通讯之几个基本问题 Android多进程之Binder的使用 Android多进程之手动编写Binder类 Android多进程之Binder解绑监听的问题 Android 多进程之Messe
  • 毕设开题分享 单片机智能教室系统(智能照明+人数统计)

    1 简介 Hi 大家好 今天向大家介绍一个学长做的单片机项目 单片机智能教室系统 智能照明 人数统计 大家可用于 课程设计 或 毕业设计 项目分享 https gitee com feifei1122 simulation project
  • Ubuntu下git提示:终止提交因为提交说明为空。

    这么简单的问题居然搜索了一会儿 现在的网络环境越来越差了 解决方法 gedit git config global core editor gedit s the paramater s means set the gedit mode t
  • 使用匿名binder实现client向server端的死亡通知

    一 引言 因为binder是跨进程通信 难免会遇到服务端出现异常挂死的情况 这个时候需要通知到客户端进行相应的处理 在网络通信中是非常常见的机制 当然 在binder中 Android也为我们写好了相关的框架 但通过binder源码分析 你
  • IBinder 解析

    远程对象的基础接口 是一个为了在执行进程中和进程间调用时的高性能 而设计的轻量级 远程调用 机制的核心部分 这个接口描述了和远程对象交互的抽象协议 不要直接实现这个接口 而是通过继承 Binder 来实现 IBinder的关键 API 是与
  • 如何避免过度努力带来的负面影响

    在快节奏的现代职场中 我们经常听到关于 努力 的话题 有些人认为 只有通过不断地努力和投入 才能够获得成功 然而 如果过度用力 可能会带来一些负面影响 本文将探讨职场中过度用力的负面影响以及如何避免这些问题 首先 让我们来看一下过度用力的负
  • Mockito3.x详解

    目录 Mockito 1 简单示例 2 打桩测试 3 参数匹配器 4 调用次数验证 5 通过打桩为无返回值函数抛出异常 6 验证调用顺序 7 验证从未发生过的交互