Mockito官方文档阅读有感——基于最新的5.0.0以上文档

根据通义千问的解释:在单元测试的上下文中,Mock 是一个专门的技术术语,指的是创建和使用模拟对象(Mock Objects)来替代真实的依赖对象进行测试的过程

Mock:是模拟的意思,指的是在测试包中创建一个结构体,满足某个外部依赖的接口 interface{}。

Stub:   是桩的意思,指的是在测试包中创建一个模拟方法,用于替换生成代码中的方法。

一、Mock, Stub,  Verify三步走

1)Mock是创建一个虚拟对象,如

//mock creation
 List mockedList = mock(List.class);

2)Stub,打桩,控制交互的行为,如

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

关于Stub的几个性质 

  • 对于任何有返回值的方法,Mock对象都会返回适当的值

  • Stub可以被覆写(Overridden)

  • 一旦被Stub,则以后不管多少次调用,都返回相同的值

  • 最后一次更重要:如果同样的方法和参数被多次Stub,最后一次生效

3)Verify, 验证调用

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

二、Mock的其他创建方法 

1、使用注解@Mock, @Spy, @Captor, @InjectMocks

前提是使用org.mockito.MockitoAnnotations进行初始化

// 返回一个AutoCloseable对象,在UT测试结束之后进行调用,跟Mockito为了实现static方法Stub有关,参考org.mockito.ScopedMock
org.mockito.MockitoAnnotations#openMocks 
// 相当于openMocks(testClass).close()
org.mockito.MockitoAnnotations#initMocks

二、关于Stub(打桩)的一些方法

1、参数匹配——org.mockito.ArgumentMatcher

当直接参数匹配不合适的时候,使用ArgumentMatcher这个会比较简便,Mockito提供了两个工具类

  • org.mockito.ArgumentMatchers,集中了一些Mockito提供的简便匹配方法

  • org.mockito.hamcrest.MockitoHamcrest,提供了Hamcrest风格的匹配方法

注意:一旦有一个参数匹配是用ArgumentMatcher,所有的参数匹配都得用ArgumentMatcher

2、Void方法需要模拟调用异常—— doThrow()方法

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

   //following throws RuntimeException:
   mockedList.clear();

3、连续Stub——给相同方法的相同参数调用模拟多次调用的不同结果

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

// 或者
// when(mock.someMethod("some arg"))
//    .thenReturn("one", "two", "three");

 //First call: throws runtime exception:
 mock.someMethod("some arg");

 //Second call: prints "foo"
 System.out.println(mock.someMethod("some arg"));

 //Any consecutive call: prints "foo" as well (last stubbing wins).
 System.out.println(mock.someMethod("some arg"));

 注意:如果不是chaining,则后面的Stub会覆盖前面的Stub

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

4、Stubbing with callbacks——让模拟过程可以有副作用

核心涉及接口org.mockito.Answers

when(mock.someMethod(anyString())).thenAnswer(
     new Answer() {
         public Object answer(InvocationOnMock invocation) {
            // 在这里可以有副作用,比如记录调用的参数之类的操作
             Object[] args = invocation.getArguments();
             Object mock = invocation.getMock();
             return "called with arguments: " + Arrays.toString(args);
         }
 });

 //Following prints "called with arguments: [foo]"
 System.out.println(mock.someMethod("foo"));

5、doReturn()|doThrow()| doAnswer()|doNothing()|doCallRealMethod() family of methods

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

   //following throws RuntimeException:
   mockedList.clear();
  • stub void methods
  • stub methods on spy objects (see below)
  • stub the same method more than once, to change the behaviour of a mock in the middle of a test.

主要针对上面三种场景,when无法解决的情况,对于第三点,说明如下:可以看到如果使用when形式的Stub,相关的mock副作用会显现

        //mock creation
        List<String> mockedList = mock(List.class);

        when(mockedList.add("1")).thenAnswer(invocation -> {
            System.out.println("调用1");
            return null;
        });

        when(mockedList.add("1")).thenAnswer(invocation -> {
            System.out.println("调用2");
            return null;
        });

        // doAnswer(invocation -> {
        //     System.out.println("调用2");
        //     return null;
        // }).when(mockedList).add("1");
        System.out.println("==============");
        mockedList.add("1");

6、想要调整Mock对象的方法调用返回的默认值——使用情况较少

例如使用org.mockito.Mockito#RETURNS_SMART_NULLS,可以在针对没有Stub的方法产生的NPE异常报告定位

        //mock creation
        List<String> mockedList = mock(List.class, RETURNS_SMART_NULLS);

        //using mock object
        mockedList.get(0).length();

执行结果显示了异常产生的位置

7、Spy,监视,"partial mocking"

   List list = new LinkedList();
   List spy = spy(list);

   //optionally, you can stub out some methods:
   when(spy.size()).thenReturn(100);

   //using the spy calls *real* methods
   spy.add("one");
   spy.add("two");

   //prints "one" - the first element of a list
   System.out.println(spy.get(0));

   //size() method was stubbed - 100 is printed
   System.out.println(spy.size());

   //optionally, you can verify
   verify(spy).add("one");
   verify(spy).add("two");

  注意:

  • Spy对象如果想要进行Stub,最好统一使用

doReturn(Object)|doThrow(Throwable...)|doThrow(Class)|doAnswer(Answer)|doNothing()|doCallRealMethod()这一系列方法,如下场景由于spy.get(0)会调用实际方法,所以会报错,实际不生效

List list = new LinkedList();
List spy = spy(list);

//Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty)
when(spy.get(0)).thenReturn("foo");

//You have to use doReturn() for stubbing
doReturn("foo").when(spy).get(0);
  • Mockito.spy()方法会对实际的对象进行复制,实际对spy对象的调用,并不会影响实际的对象
        public static class MySpy {
            int i = 0;
            public void add(int i) {
                this.i = this.i + i;
            }
            public final int get() {
                return this.i;
            }
        }
    
        public static void main(String[] args) {
            MySpy mySpy = new MySpy();
            mySpy.i = 10;
            MySpy spy = spy(mySpy);
            spy.add(10);
            System.out.println("spy.i = " + spy.i); // spy.i = 20
            System.out.println("mySpy.i = " + mySpy.i); // mySpy.i = 10
        }
  • 尽量不要去Mock final方法(但是从2.1.0开始可以,只要开启mock-maker-inline这个Mock Maker就可以,5.0.0版本开始默认开启,参考Mocking final types, enums and final methods (Since 2.1.0)

8、Real partial mocks —— 对Mock对象调用实际方法,thenCallRealMethod()

    //you can create partial mock with spy() method:
    List list = spy(new LinkedList());

    //you can enable partial mock capabilities selectively on mocks:
    Foo mock = mock(Foo.class);
    //Be sure the real implementation is 'safe'.
    //If real implementation throws exceptions or depends on specific state of the object then you're in trouble.
    when(mock.someMethod()).thenCallRealMethod();

官方不建议使用过多此种方式

9、抽象类的Spy——包括指定初始化的构造方法,非静态内部类

 //convenience API, new overloaded spy() method:
 SomeAbstract spy = spy(SomeAbstract.class);

 //Mocking abstract methods, spying default methods of an interface (only available since 2.7.13)
 Function<Foo, Bar> function = spy(Function.class);

 //Robust API, via settings builder:
 OtherAbstract spy = mock(OtherAbstract.class, withSettings()
    .useConstructor().defaultAnswer(CALLS_REAL_METHODS));

 //Mocking an abstract class with constructor arguments (only available since 2.7.14)
 SomeAbstract spy = mock(SomeAbstract.class, withSettings()
   .useConstructor("arg1", 123).defaultAnswer(CALLS_REAL_METHODS));

 //Mocking a non-static inner abstract class:
 InnerAbstract spy = mock(InnerAbstract.class, withSettings()
    .useConstructor().outerInstance(outerInstance).defaultAnswer(CALLS_REAL_METHODS));

三、关于Verify的一些方法

1、验证方法调用次数,静态方法快速入口org.mockito.Mockito,

比如atMostOnce()最少一次,never()从没调用过

verify(mockedList, times(3)).add("three times");

 //verification using never(). never() is an alias to times(0)
 verify(mockedList, never()).add("never happened");

 //verification using atLeast()/atMost()
 verify(mockedList, atMostOnce()).add("once");
 verify(mockedList, atLeastOnce()).add("three times");
 verify(mockedList, atLeast(2)).add("three times");
 verify(mockedList, atMost(5)).add("three times");

2、按顺序验证方法的调用——org.mockito.Mockito#inOrder

        //mock creation
        List<String> mockedList = mock(List.class);

        //using mock object
        mockedList.add("1");
        mockedList.add("1");
        mockedList.add("2");
        mockedList.add("1");

        //verification
        // 校验通过
        InOrder inOrder1 = inOrder(mockedList);
        inOrder1.verify(mockedList, times(2)).add("1");
        inOrder1.verify(mockedList, times(1)).add("2");
        inOrder1.verify(mockedList, times(1)).add("1");

        // 校验失败
        InOrder inOrder2 = inOrder(mockedList);
        inOrder2.verify(mockedList, times(3)).add("1");
        inOrder2.verify(mockedList, times(1)).add("2");

对于上述行为,源码中有一块注释比较耐人寻味,感兴趣可以自行深入研究一下

    /**
     * some examples how it works:
     *
     * Given invocations sequence:
     * 1,1,2,1
     *
     * if wanted is 1 and mode is times(2) then returns
     * 1,1
     *
     * if wanted is 1 and mode is atLeast() then returns
     * 1,1,1
     *
     * if wanted is 1 and mode is times(x), where x != 2 then returns
     * 1,1,1
     */
org.mockito.internal.invocation.InvocationsFinder#findMatchingChunk

3、确认没有多余的调用——org.mockito.Mockito#verifyNoMoreInteractions,org.mockito.InOrder#verifyNoMoreInteractions

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

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

 //following verification will fail
 verifyNoMoreInteractions(mockedList);

 官方建议不要过多使用此方法,因为会导致不易维护的UT

4、验证时希望拿到参数进行进一步验证——org.mockito.ArgumentCaptor

        //mock creation
        List<String> mockedList = mock(List.class);

        doReturn("1").when(mockedList).get(0);
        doReturn("2").when(mockedList).get(1);

        System.out.println("mockedList.get(0) = " + mockedList.get(0)); // mockedList.get(0) = 1
        System.out.println("mockedList.get(1) = " + mockedList.get(1)); // mockedList.get(1) = 2

        ArgumentCaptor<Integer> integerArgumentCaptor = ArgumentCaptor.forClass(int.class);
        verify(mockedList, times(2)).get(integerArgumentCaptor.capture());
        System.out.println("integerArgumentCaptor.getAllValues() = " + integerArgumentCaptor.getAllValues()); // integerArgumentCaptor.getAllValues() = [0, 1]

官方建议 ArgumentCaptor只使用在Verify里面,并且尽量使用ArgumentMatcher 

5、多线程场景下需要验证某个方法调用在规定时间内发生

   //passes when someMethod() is called no later than within 100 ms
   //exits immediately when verification is satisfied (e.g. may not wait full 100 ms)
   verify(mock, timeout(100)).someMethod();
   //above is an alias to:
   verify(mock, timeout(100).times(1)).someMethod();

   //passes as soon as someMethod() has been called 2 times under 100 ms
   verify(mock, timeout(100).times(2)).someMethod();

   //equivalent: this also passes as soon as someMethod() has been called 2 times under 100 ms
   verify(mock, timeout(100).atLeast(2)).someMethod();

四、一些特殊操作

1、重置Mock对象——org.mockito.Mockito#reset

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

   reset(mock);
   //at this point the mock forgot any interactions and stubbing

 

2、验证Mockito是否被正确使用——org.mockito.Mockito#validateMockitoUsage

默认情况下,Mockito会在下次使用相关框架方法时进行相关使用是否正确,使用validateMockitoUsage方法可以即时校验,通常在@After方法中设置,一般如果设置了runner: MockitoJUnitRunner 和rule: MockitoRule的情况下,就不需要显式调用。

举例:

        //mock creation
        List<String> mockedList = mock(List.class);

        when(mockedList.get(0));

        Mockito.validateMockitoUsage();

3、BDD(Behavior Driven Development)风格的UT—— //given //when //then 三步,org.mockito.BDDMockito

        //mock creation
        List<String> mockedList = BDDMockito.mock(List.class);

        // given
        BDDMockito.given(mockedList.get(0)).willReturn("1");

        // when
        String s = mockedList.get(0);

        // then
        Assert.assertEquals("1", s);

4、如果UT场景需要Mock对象可以序列化

对于Mock对象,通过如下方式保证可以序列化

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

 对于Spy对象,通过如下方式

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

如果需要跨类加载器,或者VM,则

 // use serialization across classloaders
 mock(Book.class, withSettings().serializable(ACROSS_CLASSLOADERS));

5、一行代码既创建Mock对象,又进行Stub

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

   @Test public void should... {}

6、忽略验证已经Stub的方法调用验证

主要结合verifyNoMoreInteractions()方法使用,官方更推荐使用 Strictness.STRICT_STUBS 特性,参考ignoreStubs方法注释

7、Mocking details ——Mock对象细节,org.mockito.MockingDetails

主要是供框架集成使用

   //To identify whether a particular object is a mock or a spy:
   Mockito.mockingDetails(someObject).isMock();
   Mockito.mockingDetails(someObject).isSpy();

   //Getting details like type to mock or default answer:
   MockingDetails details = mockingDetails(mock);
   details.getMockCreationSettings().getTypeToMock();
   details.getMockCreationSettings().getDefaultAnswer();

   //Getting invocations and stubbings of the mock:
   MockingDetails details = mockingDetails(mock);
   details.getInvocations();
   details.getStubbings();

   //Printing all interactions (including stubbing, unused stubs)
   System.out.println(mockingDetails(mock).printInvocations());

8、Delegate——Spy的补充

对于一些Spy无法成功创建的场景,可以使用Delegate进行替代

   final class DontYouDareToMockMe implements list { ... }

   DontYouDareToMockMe awesomeList = new DontYouDareToMockMe();

   List listWithDelegate = mock(List.class, AdditionalAnswers.delegatesTo(awesomeList));

   //Impossible: real method is called so listWithDelegate.get(0) throws IndexOutOfBoundsException (the list is yet empty)
   when(listWithDelegate.get(0)).thenReturn("foo");

   //You have to use doReturn() for stubbing
   doReturn("foo").when(listWithDelegate).get(0);

 需要注意的是,如果awesomeList方法里面调用了其他List接口的方法,则这些调用信息不会被Mockito框架记录

The mock that delegates simply delegates all methods to the delegate. The delegate is used all the time as methods are delegated onto it. If you call a method on a mock that delegates and it internally calls other methods on this mock, those calls are not remembered for verifications, stubbing does not have effect on them, too. Mock that delegates is less powerful than the regular spy but it is useful when the regular spy cannot be created.

9、Mockito的插件机制—— MockMaker 自定义实现Mock对象的生成

10、深度Stub

 class Lines extends List<Line> {
     // ...
 }

 lines = mock(Lines.class, RETURNS_DEEP_STUBS);

 // Now Mockito understand this is not an Object but a Line
 Line line = lines.iterator().next();

11、@Mock@Spy@InjectMocks 生效的方式

12、控制不同插件是否启用——PluginSwitch

13、自定义Verify打印错误消息

 // will print a custom message on verification failure
 verify(mock, description("This will print on failure")).someMethod();

 // will work with any verification mode
 verify(mock, times(2).description("someMethod should be called twice")).someMethod();

14、ArgumentMatcher在Stub以及Verify中的作用

        //mock creation
        List<String> mockedList = BDDMockito.mock(List.class);

        doReturn("1").when(mockedList).get(intThat(argument -> argument < 3));
        doReturn("2").when(mockedList).get(intThat(argument -> argument < 2));

        System.out.println("mockedList.get(0) = " + mockedList.get(0));
        System.out.println("mockedList.get(2) = " + mockedList.get(2));

        verify(mockedList, times(2)).get(intThat(argument -> argument < 3));
        verifyNoMoreInteractions(mockedList);

15、org.mockito.AdditionalAnswers 简化 doAnswer()方法的调用

 // Java 8 - style 1
 doAnswer(AdditionalAnswers.<String,Callback>answerVoid((operand, callback) -> callback.receive("dummy")))
     .when(mock).execute(anyString(), any(Callback.class));

 // Java 8 - style 2 - assuming static import of AdditionalAnswers
 doAnswer(answerVoid((String operand, Callback callback) -> callback.receive("dummy")))
     .when(mock).execute(anyString(), any(Callback.class));

 // Java 8 - style 3 - where mocking function to is a static member of test class
 private static void dummyCallbackImpl(String operation, Callback callback) {
     callback.receive("dummy");
 }

五、进阶了解

1、Mockito的扩展点

 Advanced public API for framework integrations (Since 2.10.+) 

2、“严格”的Mockito —— 方便debug,定位。

参考org.mockito.quality.Strictness

 

 

 

 

3、和Spring整合

主要是在Spring测试中Mockito代理类和Spring代理类冲突导致的,通过引入org.mockito.listeners.VerificationStartedListener解决

https://github.com/mockito/mockito/issues/1191

https://github.com/spring-projects/spring-boot/issues/10352

4、MockitoSession —— 作为MockitoRule 等的备用方案

6、对某个Stub或者某个Mock不使用严格校验——参考org.mockito.quality.Strictness

lenient().when(mock.foo()).thenReturn("ok"); // 对某个Stub
Foo mock = Mockito.mock(Foo.class, withSettings().lenient()); // 对某个Mock

7、可能的内存泄露问题——mock-maker-inline才可能出现此问题

经过相关资料查阅,属于当两个mock对象相互调用时,在InlineByteBuddyMockMaker内部会出现两个对象的循环引用,导致内存无法回收

 public class ExampleTest {

     @After
     public void clearMocks() {
         Mockito.framework().clearInlineMocks();
     }

     @Test
     public void someTest() {
         ...
     }
 }

8、Mock 静态方法以及构造方法——只能通过InlineMockMaker实现,从3.4,3.5版本开始支持

 

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

9、不指定参数直接Mock——从4.10.0版本开始

   Foo foo = Mockito.mock();
   Bar bar = Mockito.spy();
posted @ 2024-06-12 16:06  MarshWinter  阅读(215)  评论(0编辑  收藏  举报