Mockito 学习及使用

4 篇文章 0 订阅
3 篇文章 0 订阅

Mockito 学习并使用

1. Mock List

@Test
void action001() {
    // org.mockito.Mockito#mock()
    // mock creation
    List mockedList = mock(List.class);

    // using mock object
    mockedList.add("one");
    mockedList.clear();

    // org.mockito.Mockito#verify()
    // verification
    verify(mockedList).add("one");
    verify(mockedList).clear();
}

记住:

  1. 创建mock对象
  2. 使用mock对象(会记住相关步骤)
  3. 验证mock对象

2. Stubbing

在软件开发中通常指的是存根或桩。存根是一个占位符,用于模拟或替代尚未实现的代码部分。它允许开发人员在测试或开发过程中继续工作,而不必等待所有依赖项都完全实现。

@Test
 void action002() {
     //You can mock concrete classes, not just interfaces
     LinkedList mockedList = mock(LinkedList.class);
     // Mockito does not directly support simulation of raw data types
     // such as int, double, etc.
     // However, we can use some of the methods provided by Mockito
     // to simulate the method parameters of the original data type
     // error
     // int iValue = mock(int.class);
     // Integer intValue = mock(Integer.class);

     // org.mockito.ArgumentMatchers#intThat()
     // correct
     // int iValue = anyInt();
     // correct
     int iValue = intThat(e -> e > 100);

     // org.mockito.Mockito#when()
     // org.mockito.stubbing.OngoingStubbing#thenReturn()
     // org.mockito.stubbing.OngoingStubbing#thenThrow()
     //stubbing
     when(mockedList.get(0)).thenReturn("first");
     when(mockedList.get(1)).thenThrow(new RuntimeException());

     //following prints "first"
     System.out.println(mockedList.get(0));

     //following prints "null" because get(999) was not stubbed
     System.out.println(mockedList.get(999));

     System.out.println(iValue);

     // System.out.println(intValue);

     //Although it is possible to verify a stubbed invocation, usually it's just redundant
     //If your code cares what get(0) returns, then something else breaks (often even before verify() gets executed).
     //If your code doesn't care what get(0) returns, then it should not be stubbed.
     verify(mockedList).get(0);

     //following throws runtime exception
     System.out.println(mockedList.get(1));
 }

通常对于所有返回值的方法,模拟对象会根据需要返回 null、基本类型/包装类型的值或空集合。如,对于 int/Integer 返回 0,对于 boolean/Boolean 返回 false。

Stubbing 可以被覆盖:例如,通常的 stubbing 可以放在固定的设置中,但测试方法可以覆盖它。请注意,覆盖 stubbing 可能是一种代码异味,它暗示着过多的 stubbing。

一旦进行了 stubbing,该方法将始终返回一个 stubbed 值,不管它被调用了多少次。

3. Argument matchers

Mockito验证参数值:通常使用默认的equals()anyX() 方法。当需要额外的灵活性时,可以使用自定义参数匹配器:

@Test
void action003_001() {
    LinkedList<String> mockedList = mock(LinkedList.class);
    //stubbing using built-in anyInt() argument matcher
    when(mockedList.get(anyInt())).thenReturn("element");

    //stubbing using custom matcher (let's say isValid() returns your own matcher implementation):
    when(mockedList.contains(argThat(isValid()))).thenReturn(true);

    //following prints "element"
    System.out.println(mockedList.get(999));

    //you can also verify using an argument matcher
    verify(mockedList).get(anyInt());
    System.out.println(mockedList.get(anyInt()));

    mockedList.add("12345");
    mockedList.add("3456789");

    //argument matchers can also be written as Java 8 Lambdas
    verify(mockedList).add(argThat(someString -> someString.length() > 5));
}

private ArgumentMatcher isValid() {
    return obj -> {
        System.out.println("obj -> " + obj);
        return obj != null;
    };
}

使用参数匹配器需注意:
如果使用参数匹配器,所有参数都必须由匹配器提供。

//correct - eq() is also an argument matcher
verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));

//incorrect - exception will be thrown because third argument is given without an argument matcher.
verify(mock).someMethod(anyInt(), anyString(), "third argument");

matcher 方法如 any()eq() 并不返回匹配器。在内部,它们将匹配器记录在堆栈上,并返回一个虚拟值(通常为 null)。这种实现是由Java 编译器所施加的静态类型安全所导致的。其结果是你不能在验证/存根方法之外使用 any()eq() 方法。

Java 编译器所施加的静态类型安全 ---- 在Java中,编译器在编译时会检查类型,以确保类型匹配。因此,在某些情况下,为了满足编译器的类型检查,需要采取特定的编程实现方式。
如: Person.class,有一个方法 getName() ,而当尝试调用一个不存在的方法 getAge()。由于Java编译器的静态类型安全性,编译器会在编译时报错,因为在 Person.class 中并没有定义 getAge() 方法。

@Test
void action003_002() {
     class Person {
         private int age;
         private String firstName;
         private String lastName;

         public String updatePerson(int age, String firstName, String lastName) {
             this.age = age;
             this.firstName = firstName;
             this.lastName = lastName;
             return "updated";
         }
     }
     Person mock = mock(Person.class);
     String result = mock.updatePerson(41, "Fernando", "Shui");
     System.out.println(result);
     //correct - eq() is also an argument matcher
     verify(mock).updatePerson(anyInt(), anyString(), eq("Shui"));

     //incorrect - exception will be thrown because third argument is given without an argument matcher.
     //verify(mock).updatePerson(anyInt(), anyString(), "third argument");
 }

4. Verifying exact number of invocations / at least x / never

@Test
void action004() {
    LinkedList<String> mockedList = mock(LinkedList.class);

    //using mock
    mockedList.add("once");

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

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

    // org.mockito.Mockito#times()
    //following two verifications work exactly the same - times(1) is used by default
    verify(mockedList).add("once");
    verify(mockedList, times(1)).add("once");

    //exact number of invocations verification
    verify(mockedList, times(2)).add("twice");
    verify(mockedList, times(3)).add("three times");

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

    // org.mockito.Mockito#atMostOnce()
    // org.mockito.Mockito#atLeastOnce()
    // org.mockito.Mockito#atLeast()
    // org.mockito.Mockito#atMost()
    //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");
}

times(1) 是verify 次数默认值。never()times(0)

5. Stubbing void methods with exceptions

@Test
void action005() {
    LinkedList<String> mockedList = mock(LinkedList.class);
    // org.mockito.Mockito#doThrow()
    doThrow(new RuntimeException()).when(mockedList).clear();

    try {
        //following throws RuntimeException:
        mockedList.clear();
        // org.junit.jupiter.api.Assertions#fail()
        fail("RuntimeException");
    } catch (RuntimeException re) {
        // runtimeException => java.lang.RuntimeException
        System.out.println("runtimeException => " + re);
    }
}

6. Verification in order

@Test
void action006() {
    // A. Single mock whose methods must be invoked in a particular order
    List singleMock = mock(List.class);

    //using a single mock
    singleMock.add("was added first");
    singleMock.add("was added second");

    // org.mockito.InOrder#inOrder()
    //create an inOrder verifier for a single mock
    InOrder inOrder = inOrder(singleMock);

    //following will make sure that add is first called with "was added first", then with "was added second"
    inOrder.verify(singleMock).add("was added first");
    inOrder.verify(singleMock).add("was added second");

    // error
    // inOrder.verify(singleMock).add("was added second");
    // inOrder.verify(singleMock).add("was added first");

    // B. Multiple mocks that must be used in a particular order
    List firstMock = mock(List.class);
    List secondMock = mock(List.class);

    //using mocks
    firstMock.add("was called first");
    secondMock.add("was called second");

    //create inOrder object passing any mocks that need to be verified in order
    InOrder inOrderWith2Params = inOrder(secondMock, firstMock);
    // correct also
    // InOrder inOrderWith2Params = inOrder(firstMock, secondMock);

    //following will make sure that firstMock was called before secondMock
    inOrderWith2Params.verify(firstMock).add("was called first");
    inOrderWith2Params.verify(secondMock).add("was called second");

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

顺序验证非常灵活 - 不必逐个验证所有的交互,而只需要验证我们感兴趣的按顺序测试的交互。

7. Making sure interaction(s) never happened on mock

@Test
void action007() {
    List mockOne = mock(List.class);
    //using mocks - only mockOne is interacted
    mockOne.add("one");

    //ordinary verification
    verify(mockOne).add("one");

    //verify that method was never called on a mock
    verify(mockOne, never()).add("two");
}

8. Finding redundant invocations

@Test
void action008_001() {
    List mockedList = mock(List.class);
    //using mocks
    mockedList.add("one");
    mockedList.add("two");

    // comment one of below line code, will fail when run at verifyNoMoreInteractions()
    verify(mockedList).add("one");        
    verify(mockedList).add("two");

    // org.mockito.Mockito#verifyNoMoreInteractions()
    verifyNoMoreInteractions(mockedList);
}

@Test
void action008_002() {
    List mockedList = mock(List.class);
    //using mocks
    mockedList.add("one");
    mockedList.add("two");

    // comment one of below line code, will fail when run at verifyNoMoreInteractions()
    verify(mockedList).add("one");
    verify(mockedList).add("two");

    verify(mockedList, never()).add("never add element");
}

通常使用“期望-运行-验证”模拟的用户倾向于使用 verifyNoMoreInteractions(),甚至在每个测试方法中都使用它。但不建议在每个测试方法中都使用 verifyNoMoreInteractions()。verifyNoMoreInteractions() 是交互测试工具包中的一种方便的断言。只有在需要时才使用它。滥用它会导致过度指定、难以维护的测试。

9. Shorthand for mocks creation - @Mock annotation

  • 减少模拟对象代码创建。
  • 提高测试类的可读性。
  • 通过使用字段名称来识别模拟对象,使验证错误更易于理解。
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) {

10. Stubbing consecutive calls (iterator-style stubbing)

 @Test
void action010() {
    class Order {
        public String getOrderDetail(String orderNo) {
            return "order detail is searched by orderNo " + orderNo;
        }
    }

    Order order = mock(Order.class);

    when(order.getOrderDetail("O00001"))
            .thenThrow(new RuntimeException())
            .thenReturn("O0000x");

    //First call: throws runtime exception:
    try {
        order.getOrderDetail("O00001");
        fail("RuntimeException must be thrown above line code");
    } catch (RuntimeException re) {
        System.out.println("order#getOrderDetail() exception has been caught.");
    }

    //Second call: prints "O0000x"
    assertEquals("O0000x", order.getOrderDetail("O00001"));

    //Any consecutive call: prints "O0000x" as well (last stubbing wins).
    assertEquals("O0000x", order.getOrderDetail("O00001"));
}

11. Stubbing with callbacks

@Test
void action011() {
    class Order {
        public String getOrderDetail(String orderNo) {
            return "order detail is searched by orderNo " + orderNo;
        }
    }

    Order order = mock(Order.class);
    // org.mockito.stubbing.OngoingStubbing.thenAnswer()
    // org.mockito.invocation.InvocationOnMock
    when(order.getOrderDetail(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: [O00002]"
    System.out.println(order.getOrderDetail("O00002"));
}

12. doReturn() | doThrow() | doAnswer() | doNothing() | doCallRealMethod() family of methods

public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
    public int sub(int a, int b) {
        return a - b;
    }
}
public class CalculatorService {
    private Calculator calculator;
    public CalculatorService(Calculator calculator) {
        this.calculator = calculator;
    }
    public int performAddition(int a, int b) {
        // Some additional logic
        return calculator.add(a, b);
    }
}
@Test
void action012() {
    List mockedList = mock(List.class);
    doReturn("hello").when(mockedList).get(0);
    assertEquals("hello", mockedList.get(0));

    doNothing().when(mockedList).clear();
    mockedList.clear();

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

    try {
        // following throws RuntimeException:
        mockedList.clear();
        fail("RuntimeException");
    } catch (RuntimeException re) {
        // runtimeException => java.lang.RuntimeException
        System.out.println("runtimeException => " + re);
    }
}

/**
 * doCallRealMethod()
 */
@Test
void action012_002() {
    // Create a mock of Calculator
    Calculator mockCalculator = mock(Calculator.class);

    // Set up mock behavior
    when(mockCalculator.add(2, 3)).thenReturn(5); // Mocking add behavior

    // org.mockito.Mockito#doCallRealMethod()
    // Call real method on mock for specific arguments
    doCallRealMethod().when(mockCalculator).add(5, 5);

    // Create instance of CalculatorService with the mock
    CalculatorService calculatorService = new CalculatorService(mockCalculator);

    // Perform the test
    int result1 = calculatorService.performAddition(2, 3);
    int result2 = calculatorService.performAddition(5, 5);

    // Verify the results
    //
    // Assert that mocked behavior works
    assertEquals(5, result1);
    // Assert that real behavior works
    assertEquals(10, result2);
}

Q: @Mock@InjectMock 区别?
A: > @Mock – 创建模拟, @InjectMock – 创建对象并注入模拟的依赖项

  • 使用@InjectMock创建需要在测试类中进行测试的类实例。
  • 当需要为给定类执行实际方法主体时,请使用@InjectMock。
  • 当我们需要使用模拟对象初始化的所有内部依赖项以正常工作方法时,请使用@InjectMock。
  • 使用@Mock创建模拟,以支持要测试的类的测试。
  • 带有@Mock注解的带注解的类(待测试)依赖项。
  • 必须为模拟对象定义when() – thenReturn(),在实际测试执行期间将调用哪些类方法。

13. Spying on real objects

可以创建真实对象的Spy。当使用Spy时,会调用真实的方法(除非方法被存根化)。
Spy应该谨慎使用,只在必要时使用,比如处理旧代码时。
对真实对象进行Spy可与“部分模拟”概念相关联。在1.8版本之前,Mockito的Spy不是真正的部分模拟。原因是普遍都认为部分模拟是代码异味,对旧代码的临时重构除外。

@Test
void action013_001() {
    List list = new LinkedList();
    // org.mockito.Mockito#spy()
    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");
}

有时使用 when(Object) 进行存根化Spy是不可能的。因此,在使用Spy时,请考虑使用 doReturn | doAnswer | doThrow() 方法系列进行存根化。

Mockito 不会将委托调用传递的真实对象实例,只会创建其副本。如果你保留了真实对象实例并与之交互,请不要指望Spy会意识到这些交互及其对真实实例状态的影响。

注意 final 方法。Mockito 不会模拟 final 方法。

@Test
void action013_002() {
    List list = new LinkedList();
    List spy = spy(list);

    //Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty)
    try {
        when(spy.get(0)).thenReturn("foo");
        fail("Index Out of Bounds Exception");
    } catch (IndexOutOfBoundsException ioobe) {
        System.out.println("IndexOutOfBoundsException => " + ioobe);
    }

    //You have to use doReturn() for stubbing
    doReturn("foo").when(spy).get(0);
    // now it prints what we want, foo => foo
    System.out.println("foo => " + spy.get(0));
}

14. Changing default return values of un-stubbed invocations (Since 1.7)

@Test
void action014() {
    class Person {
        private String name;
        public Person(String name) {
            this.name = name;
        }
        public String getName() {
            return this.name;
        }
        public String getFullName() {
            return "fullName > ";
        }
    }
    // default return value
    class YourOwnAnswer implements Answer {
        @Override
        public Object answer(InvocationOnMock invocation) throws Throwable {
            return "You see me";
        }
    }
    Person mockPerson1 = mock(Person.class, Mockito.RETURNS_SMART_NULLS);
    Person mockPerson2 = mock(Person.class, new YourOwnAnswer());

    //
    System.out.println(mockPerson1.getName());
    // You see me
    System.out.println(mockPerson2.getName());
    //
    System.out.println(mockPerson1.getFullName());
    // You see me
    System.out.println(mockPerson2.getFullName());

    when(mockPerson1.getName()).thenReturn("1mk -> ");
    when(mockPerson2.getName()).thenReturn("2mk -> ");
    // 1mk ->
    System.out.println(mockPerson1.getName());
    // 2mk ->
    System.out.println(mockPerson2.getName());
    
    when(mockPerson1.getFullName()).thenReturn("3mk -> ");
    when(mockPerson2.getFullName()).thenReturn("4mk -> ");
    // 3mk ->
    System.out.println(mockPerson1.getFullName());
    // 4mk ->
    System.out.println(mockPerson2.getFullName());
}

public static <T> T mock(Class<T> classToMock, Answer defaultAnswer)
只有在你没有对方法调用进行存根化时才会使用默认答案。

15. Capturing arguments for further assertions (Since 1.8.0)

@Test
void action015() {
    class Person {
        private String name;
        public Person(String name) {
            this.name = name;
        }
        public String getName() {
            return this.name;
        }
    }

    class Group {
        private List<Person> persons = new ArrayList<>();
        public void addPerson(Person person) {
            persons.add(person);
        }
    }
    Group mock = mock(Group.class);
   
    mock.addPerson(new Person("John"));
    // Create an ArgumentCaptor of type Person
    ArgumentCaptor<Person> argument = ArgumentCaptor.forClass(Person.class);
    // Verify that addPerson() is called and capture the parameters passed
    verify(mock).addPerson(argument.capture());
    assertEquals("John", argument.getValue().getName());
}

ArgumentCaptor 在某些方式上与ArgumentMatcher自定义参数匹配器通用。他们都可以用于确保某些参数被传递给模拟对象。但ArgumentCaptor 可能更适合应用于以下2种场景:

  • 自定义参数匹配器不太可能被重复使用。
  • 只对参数值进行断言就可以完成验证。
    通过 ArgumentMatcher 的自定义参数匹配器通常更适合用于存根化。

16. Real partial mocks (Since 1.8.0)

@Test
void action016() {
    class Person {
        private String name;
        public Person(String name) {
            this.name = name;
        }
        public String getName() {
            return "name -> " + this.name;
        }

        public String getFullName() {
            throw new RuntimeException("Not implement");
        }
    }
    // 使用 spy() 方法可以创建部分模拟对象:
    List list = spy(new LinkedList());

    // 允许有选择地在模拟对象上启用部分模拟能力:
    Person mock = mock(Person.class);
    // 确保真实实现是“安全”的。
    when(mock.getName()).thenCallRealMethod();
    assertEquals("name -> null", mock.getName());
    when(mock.getFullName()).thenCallRealMethod();
    try {
        System.out.println(mock.getFullName());
        fail("Runtime Exception");
    } catch (RuntimeException re) {
        System.out.println("RuntimeException => " + re);
    }
}

17. Resetting mocks (Since 1.8.0)

@Test
void action017() {
    List mock = mock(List.class);
    when(mock.size()).thenReturn(10);
    mock.add(1);
	// org.mockito.Mockito#reset()
    reset(mock);
    // the mock forgot any interactions and stubbing
    // 0
    System.out.println(mock.size());
}

19. Aliases for behavior driven development (Since 1.8.0)

行为驱动开发(Behavior Driven Development,简称BDD)风格的测试方法使用 //given //when //then注释作为测试方法的基本部分。

@Test
void action019() {
    Seller seller = mock(Seller.class);
    Shop shop = new Shop(seller);

    // org.mockito.BDDMockito#given()
    // org.mockito.BDDMockito#willReturn()
    // given
    given(seller.askForBread()).willReturn(new Bread());

    //when
    Goods goods = shop.buyBread();

    // org.junit.jupiter.api.Assertions#assertNotNull()
    // org.junit.jupiter.api.Assertions#assertTrue()
    // then
    assertNotNull(goods);
    assertTrue(goods.containsBread());
}

class Bread {
}
class Seller {
    public Bread askForBread() {
        return new Bread();
    }
}
class Goods {
    private List<Object> items;
    public Goods() {
        this.items = new ArrayList<>();
    }
    public void addItem(Object item) {
        this.items.add(item);
    }
    public boolean containsBread() {
        return items.stream().anyMatch(e -> e instanceof Bread);
    }
}
class Shop {
    private Seller seller;
    public Shop(Seller seller) {
        this.seller = seller;
    }
    public Goods buyBread() {
        Goods goods = new Goods();
        Bread bread = seller.askForBread();
        goods.addItem(bread);
        return goods;
    }
}

行为驱动开发(Behavior-Driven DevelopmentBDD)的概念来自于测试驱动开发,强调使用DSL(Domain Specific Language,领域特定语言)描述用户行为,定义业务需求,是需求分析人员、开发人员与测试人员进行沟通的有效方法。DSL是一种编码实现,相比自然语言更加精确,又能以符合领域概念的形式满足所谓“动态文档(Living Document)”的要求。-- 行为驱动开发将编码实现与业务行为描述有效地结合起来。

20. Serializable mocks (Since 1.8.1)

Mocks可以被序列化。你可以在需要依赖项可序列化的地方使用一个mock。(在单元测试中应该很少使用。)
该行为是为了一个具体的用例而实现的BDD规范,该用例有一个不可靠的外部依赖。如在web环境中,有外部依赖项的对象被序列化后在各层之间传递。

@Test
void action020() {

     List normalMock = mock(List.class);
     // org.mockito.Mockito#withSettings()
     List serializableMock = mock(List.class, withSettings().serializable());

     normalMock.add("2");
     normalMock.add("7");

     serializableMock.add("1");
     serializableMock.add("5");

     try {
         serialize(normalMock, "normalMock.txt");
         fail("RuntimeException");
     }catch (RuntimeException re) {
         // RuntimeException java.io.NotSerializableException: org.mockito.codegen.List$MockitoMock$BVSjR2tH
         System.out.println("RuntimeException " + re.getMessage());
     }

     serialize(serializableMock, "serializableMock.txt");

     try {
         normalMock = (List) deSerialize("normalMock.txt");
         fail("RuntimeException");
     }catch (RuntimeException re) {
         // RuntimeException java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: org.mockito.codegen.List$MockitoMock$BVSjR2tH
         System.out.println("RuntimeException " + re.getMessage());
     }

     serializableMock = (List) deSerialize("serializableMock.txt");

     // as above serialize and deserialize havent impacted normalMock.
     // it runs successfully as normal logic
     verify(normalMock).add("2");
     verify(normalMock).add("7");

     // as above serialize and deserialize have impacted serializableMock.
     // it runs successfully becaz now it has been deserialized.
     verify(serializableMock).add("1");
     verify(serializableMock).add("5");

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

21. New annotations: @Captor, @Spy, @InjectMocks (Since 1.8.3)

@Captor 简化了 ArgumentCaptor 的创建 - 当要捕获的参数是复杂的泛型类并且想要避免编译器警告时很有用。
@Spy 可以使用它代替 spy(Object)
@InjectMocks 自动将 mock 或 spy 属性注入到被测试的对象中。
上面三个注解只在 MockitoAnnotations.openMocks(Object) 上进行处理。就像对 @Mock 注解一样,可以使用内置的运行器:MockitoJUnitRunner 或规则:MockitoRule。

22. Verification with timeout (Since 1.8.5)

超时验证 - 会导致验证等待指定的时间段以进行所需的交互,而不是在尚未发生时立即失败。 并发条件下的测试可能有用。

@Test
void action022() {
    TaskManager taskManager = Mockito.mock(TaskManager.class);

    simulateTaskAddAction(taskManager, 50);
    
    //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(taskManager, timeout(100)).addTask("Task 1");
    //above is an alias to:
    verify(taskManager, timeout(100).times(1)).addTask("Task 1");

    simulateTaskAddAction(taskManager, 50);
    //passes as soon as someMethod() has been called 2 times under 100 ms
    verify(taskManager, timeout(100).times(2)).addTask("Task 1");

    //equivalent: this also passes as soon as someMethod() has been called 2 times under 100 ms
    verify(taskManager, timeout(100).atLeast(2)).addTask("Task 1");
}

private void simulateTaskAddAction(TaskManager taskManager, long delay) {
    new Timer().schedule(new TimerTask() {
        @Override
        public void run() {
            taskManager.addTask("Task 1");
        }
    }, delay);
}

static class TaskManager {
    public void addTask(String task) {
        // TODO: adding task
    }
}

23. Automatic instantiation of @Spies, @InjectMocks and constructor injection goodness (Since 1.9.0)

@Spy 用于创建真实对象的部分模拟,而 @InjectMocks 用于创建测试对象并注入模拟对象。它们的主要区别在于:

@Spy 用于创建一个实际对象的部分模拟,通常是在已有对象的基础上,部分方法进行模拟。它不会创建新的对象,而是使用已有的对象。
@InjectMocks 用于创建测试对象并注入模拟对象。它会创建一个新的测试对象,并尝试将模拟对象注入到测试对象中。注入可以通过构造函数注入、setter 方法注入或字段注入来实现,具体取决于对象的构造和注入方式。

要利用此功能,您需要使用 MockitoAnnotations.openMocks(Object)、MockitoJUnitRunner 或 MockitoRule。

 //instead:
 @Spy 
 BeerDrinker drinker = new BeerDrinker();
 //you can write:
 @Spy 
 BeerDrinker drinker;

 //same applies to @InjectMocks annotation:
 @InjectMocks 
 LocalPub;

24. One-liner stubs (Since 1.9.0)

Mockito 允许我们在存根时创建模拟对象。 它允许在一行代码中创建一个存根。 这有助于保持测试代码的整洁。

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

   @Test 
   public void should... {}

25. Verification ignoring stubs (Since 1.9.0)

Mockito 允许忽略存根进行验证。 有时与 verifyNoMoreInteractions() 或验证 inOrder() 结合使用。 有助于避免对存根调用进行冗余验证 - 除非对验证存根不感兴趣。

ignoreStubs() 可能会导致过度使用 verifyNoMoreInteractions(ignoreStubs(...)); 请记住,Mockito 不建议每个测试中都使用 verifyNoMoreInteractions()

class Foo {
    public String foo() {
        return "foo";
    }
}
class Bar {
    public String bar() {
        return "bar";
    }
}
@Test
void action025_001() {

    Foo mock = mock(Foo.class);
    Bar mockTwo = mock(Bar.class);
    when(mock.foo()).thenReturn("fooX");
    when(mockTwo.bar()).thenReturn("barX");

    mock.foo();
    mockTwo.bar();
    verify(mock).foo();

    // org.mockito.Mockito#verifyNoMoreInteractions()
    // verifyNoMoreInteractions() fails because mockTwo.bar() methods were not accounted for.
    // verifyNoMoreInteractions(mock, mockTwo);
    
    // org.mockito.Mockito#ignoreStubs()
    // successfully as ignore Stubbing mockTwo.bar()
    verifyNoMoreInteractions(ignoreStubs(mock, mockTwo));
}

@Test
void action025_002() {
    Foo mock = mock(Foo.class);
    Bar mockTwo = mock(Bar.class);
    when(mock.foo()).thenReturn("fooX");
    when(mockTwo.bar()).thenReturn("barX");
    mock.foo();
    mockTwo.bar();
    verify(mockTwo).bar();
    // creates InOrder that will ignore stubbed
    InOrder inOrder = inOrder(ignoreStubs(mock, mockTwo));
    mockTwo.bar();
    mock.foo();
    inOrder.verify(mockTwo).bar();
    inOrder.verify(mock).foo();

    inOrder.verifyNoMoreInteractions();
}

26. Mocking details (Improved in 2.2.x)

Mockito 提供 API 来检查模拟对象的详细信息。 此 API 对于高级用户和模拟框架集成商非常有用。

public class Mockito006 {
    class Person {
        private String name;
        private int age;
        private String gender;
        public Person(String name, int age, String gender) {
            this.name = name;
            this.age = age;
            this.gender = gender;
        }
        public String getName() {
            return name;
        }
        public int getAge() {
            return age;
        }
        public String getGender() {
            return gender;
        }
    }
    @Test
    void action026() {
        Person person = mock(Person.class);
        when(person.getName()).thenReturn("Fernando");
        when(person.getAge()).thenReturn(24);
        when(person.getGender()).thenReturn("Male");
        // Fernando
        System.out.println(person.getName());
        // 24
        System.out.println(person.getAge());
        verify(person).getName();
        //To identify whether a particular object is a mock or a spy:
        // true
        System.out.println(mockingDetails(person).isMock());
        // false
        System.out.println(mockingDetails(person).isSpy());

        //Getting details like type to mock or default answer:
        MockingDetails details = mockingDetails(person);
        // class pr.iceworld.fernando.Mockito006$Person
        System.out.println(details.getMockCreationSettings().getTypeToMock());
        // RETURNS_DEFAULTS
        System.out.println(details.getMockCreationSettings().getDefaultAnswer());

        //Getting invocations and stubbings of the mock:
        details = mockingDetails(person);
        
        // [person.getName();, person.getAge();]
        System.out.println(details.getInvocations());
        // [person.getName(); stubbed with: [Returns: Fernando], person.getAge(); stubbed with: [Returns: 24], person.getGender(); stubbed with: [Returns: Male]]
        System.out.println("----------2");
        System.out.println(details.getStubbings());
        //Printing all interactions (including stubbing, unused stubs)
        // [Mockito] Interactions of: Mock for Person, hashCode: 1584918772
        //  1. person.getName();
        //   -> at pr.iceworld.fernando.Mockito006.action026(Mockito006.java:35)
        //    - stubbed -> at pr.iceworld.fernando.Mockito006.action026(Mockito006.java:31)
        //  2. person.getAge();
        //   -> at pr.iceworld.fernando.Mockito006.action026(Mockito006.java:37)
        //    - stubbed -> at pr.iceworld.fernando.Mockito006.action026(Mockito006.java:32)
        // [Mockito] Unused stubbings of: Mock for Person, hashCode: 1584918772
        //  1. person.getName();
        //   - stubbed -> at pr.iceworld.fernando.Mockito006.action026(Mockito006.java:31)
        //  2. person.getAge();
        //   - stubbed -> at pr.iceworld.fernando.Mockito006.action026(Mockito006.java:32)
        //  3. person.getGender();
        //   - stubbed -> at pr.iceworld.fernando.Mockito006.action026(Mockito006.java:33)
        System.out.println(details.printInvocations());
    }
}

29. BDD style verification (Since 1.10.0)

BDD 风格的验证通常使用 “given-when-then” 结构。在 Mockito 中,可以通过 BDDMockito 类来实现 BDD 风格的验证。

class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
    public int sub(int a, int b) {
        return a - b;
    }
}

@Test
public void testAddition() {
    // given
    Calculator calculator = mock(Calculator.class);
    given(calculator.add(2, 3)).willReturn(5);
    // when
    int result = calculator.add(2, 3);
    // then
    then(calculator).should().add(2, 3);
    assertEquals(5, result);
}

@Test
public void testSubtraction() {
    // given
    Calculator calculator = mock(Calculator.class);
    given(calculator.sub(5, 3)).willReturn(2);
    // when
    int result = calculator.sub(5, 3);
    // then
    then(calculator).should().sub(5, 3);
    assertEquals(2, result);
}

30. Spying or mocking abstract classes (Since 1.10.12, further enhanced in 2.7.13 and 2.7.14)

现在可以方便地对抽象类进行spy操作。请注意,过度使用spy可能暗示着代码设计存在问题(请参阅 spy(Object))。以前,只能在对象的实例上进行spy操作。新的 API 使得在创建模拟对象实例时可以使用构造函数。这对于模拟抽象类特别有用,因为用户不再需要提供抽象类的实例。目前,仅支持无参数的构造函数。

public abstract class AbstractFruit {
    private String name;
    private int size;

    public AbstractFruit(String name, int size) {
        this.name = name;
        this.size = size;
    }
    public AbstractFruit() {
    }
    public abstract double cost();
}

public interface Fruitable {
    double cost();
}

public class OuterClass {
    public abstract class InnerClass {
        public String getSerialNo() {
            return UUID.randomUUID().toString();
        }
    }
}

@Test
void action030() {
     List list = spy(List.class);
     // false
     System.out.println(list.contains("x"));
     when(list.contains("x")).thenReturn(Boolean.TRUE);
     // true
     System.out.println(list.contains("x"));

     Fruitable fruitable = spy(Fruitable.class);
     // 0.0
     System.out.println(fruitable.cost());
     when(fruitable.cost()).thenReturn(45.0d);
     // 45.0
     System.out.println(fruitable.cost());

     //convenience API, new overloaded spy() method:
     AbstractFruit spyFruit = spy(AbstractFruit.class);
     // 0.0
     System.out.println(spyFruit.cost());
     when(spyFruit.cost()).thenReturn(10.0d);
     // 10.0
     System.out.println(spyFruit.cost());

     //Mocking abstract methods, spying default methods of an interface (only available since 2.7.13)
     Function<String, String> function = spy(Function.class);
     // null
     System.out.println(function.apply("x"));
     when(function.apply("x")).thenReturn("y");
     // y
     System.out.println(function.apply("x"));

     //Robust API, via settings builder:
     Fruitable spyFruitable = mock(Fruitable.class, withSettings()
             .useConstructor().defaultAnswer(CALLS_REAL_METHODS));
     // 0.0
     System.out.println(spyFruitable.cost());
     when(spyFruitable.cost()).thenReturn(20.0d);
     // 20.0
     System.out.println(spyFruitable.cost());


     AbstractFruit spyAbstractFruit = mock(AbstractFruit.class, withSettings()
             .useConstructor().defaultAnswer(CALLS_REAL_METHODS));

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

     //Mocking a non-static inner abstract class:
     OuterClass.InnerClass spyOI = mock(OuterClass.InnerClass.class, withSettings()
             .useConstructor().outerInstance(new OuterClass()).defaultAnswer(CALLS_REAL_METHODS));
     // 35bd42ff-b12f-4ef7-a6a9-f10b2d089a83
     System.out.println(spyOI.getSerialNo());
     when(spyOI.getSerialNo()).thenReturn("serialNo");
     // serialNo
     System.out.println(spyOI.getSerialNo());
 }

Mockito mocks can be serialized / deserialized across classloaders (Since 1.10.0)

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

public class Book implements Serializable {
     public String getTitle() {
         return "The Book of Doors";
     }
 }

 static final String acrossClsBookTitle = "across classloader get title";
 static final String sameClsBookTitle = "same classloader get title";

 @Test
 void action031_001() throws IOException, ClassNotFoundException {
     // use regular serialization
     Book mockedBookSameClassLoader = mock(Book.class, withSettings().serializable());
     when(mockedBookSameClassLoader.getTitle()).thenReturn(sameClsBookTitle);
     // Serialization and deserialization within the same classloader
     byte[] serializedSameClassLoader = serialize(mockedBookSameClassLoader);
     Book deserializedSameClassLoader = deserialize(serializedSameClassLoader);
     assertEquals(sameClsBookTitle, deserializedSameClassLoader.getTitle());
 }

 // can separate run action031_002 and action031_003
 @Test
 void action031_002() throws Exception {
     // use serialization across classloaders
     Book mockedBookAcrossClassLoaders = mock(Book.class, withSettings().serializable(ACROSS_CLASSLOADERS));
     // Serializing the mocked book
     when(mockedBookAcrossClassLoaders.getTitle()).thenReturn(acrossClsBookTitle);
     serializeAcrossCl(mockedBookAcrossClassLoaders);
 }

 @Test
 void action031_003() throws Exception {
      // Serializing the mocked book
     Book deSerialBook = deserializeAcrossCl();
     assertEquals(acrossClsBookTitle, deSerialBook.getTitle());
 }

 private static void serializeAcrossCl(Object object) throws Exception {
     try (FileOutputStream fileOut = new FileOutputStream("serializedBook.ser");
          ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
         out.writeObject(object);
         System.out.println("Object serialized successfully.");
     }
 }

 private static Book deserializeAcrossCl() throws Exception {
     try (FileInputStream fileIn = new FileInputStream("serializedBook.ser");
          ObjectInputStream in = new ObjectInputStream(fileIn)) {
         Book book = (Book) in.readObject();
         System.out.println("Object deserialized successfully.");
         return book;
     }
 }

 private static byte[] serialize(Book book) throws IOException {
     try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
          ObjectOutputStream out = new ObjectOutputStream(bos)) {
         out.writeObject(book);
         System.out.println("Object serialized successfully.");
         return bos.toByteArray();
     }
 }

 private static Book deserialize(byte[] data) throws IOException, ClassNotFoundException {
     try (ByteArrayInputStream bis = new ByteArrayInputStream(data);
          ObjectInputStream in = new ObjectInputStream(bis)) {
         Book book = (Book) in.readObject();
         System.out.println("Object deserialized successfully.");
         return book;
     }
 }

32. Better generic support with deep stubs (Since 1.10.0)

深度存根(Deep stubbing)如果类中有可用的泛型信息,它将能够找到该信息。使用类似结构的类,而无需模拟其行为。

class Line {
}
class Lines extends ArrayList<Line> {
}

@Test
void action032() {
    Lines lines = mock(Lines.class, RETURNS_DEEP_STUBS);
    // Now Mockito understand this is not an Object but a Line
    Line line = lines.iterator().next();
}

33. Mockito JUnit rule (Since 1.10.17)

JUnit5 doesnt support

@RunWith(MockitoJUnitRunner.class)

but

@ExtendWith(MockitoExtension.class)

34. Switch on or off plugins (Since 1.10.15)

在mockito 中,有一个孵化功能可以切换mockito 插件。

@ExtendWith(MockitoExtension.class)
public class PluginToggleExample {

    @Mock
    private ValuePlugin valuePlugin;

    @Test
    void PluginToggleExample() {
        // Initialize the plugin switch
        PluginSwitch pluginSwitch = mock(PluginSwitch.class);
        //
        String pluginClzName = ValuePlugin.class.getName();
        when(pluginSwitch.isEnabled(pluginClzName)).thenReturn(true);

        // Toggle the plugin behavior using the switch
        when(valuePlugin.getValue()).thenAnswer(
                invocation -> pluginSwitch.isEnabled(pluginClzName) ? 1 : 2);

        // Verify initial behavior with plugin switch enabled
        assertEquals(1, valuePlugin.getValue());

        // Disable the plugin switch
        when(pluginSwitch.isEnabled(pluginClzName)).thenReturn(false);

        // Verify behavior after disabling the plugin switch
        assertEquals(2, valuePlugin.getValue());

        // Enable the plugin switch again
        when(pluginSwitch.isEnabled(pluginClzName)).thenReturn(true);

        // Verify behavior after enabling the plugin switch again
        assertEquals(1, valuePlugin.getValue());
    }
}

public interface ValuePlugin {
    int getValue();
}

35. Custom verification failure message (Since 2.1.0)

public class Person extends AbsPerson {

    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        LastName = lastName;
    }

    public Person() {
    }

    private String firstName;
    private String LastName = "Shui";
    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return LastName;
    }

    public String characteristics() {
        return "Normal";
    }

    public String getIdCard() {
        return "";
    }
}

@Test
void action035_001() {
    Person person = mock(Person.class);
    // will print a custom message on verification failure
    verify(person, description("This will print on failure")).getIdCard();
}

执行以上action035_001() 提示 getIdCard should be called twice

org.mockito.exceptions.base.MockitoAssertionError: This will print on failure

Wanted but not invoked:
person.getIdCard();
-> at pr.iceworld.fernando.Person.getIdCard(Person.java:28)
Actually, there were zero interactions with this mock.


	at pr.iceworld.fernando.Person.getIdCard(Person.java:28)
	at pr.iceworld.fernando.M35_CustomVerificationFailureMessage.action035_001(M35_CustomVerificationFailureMessage.java:15)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
@Test
void action035_002() {
    Person person = mock(Person.class);
    person.getIdCard();
    // will work with any verification mode
    verify(person, times(2).description("getIdCard should be called twice")).getIdCard();
}

执行以上action035_002() 提示 getIdCard should be called twice

org.mockito.exceptions.base.MockitoAssertionError: getIdCard should be called twice

person.getIdCard();
Wanted 2 times:
-> at pr.iceworld.fernando.Person.getIdCard(Person.java:28)
But was 1 time:
-> at pr.iceworld.fernando.M35_CustomVerificationFailureMessage.action035_002(M35_CustomVerificationFailureMessage.java:21)


	at pr.iceworld.fernando.Person.getIdCard(Person.java:28)
	at pr.iceworld.fernando.M35_CustomVerificationFailureMessage.action035_002(M35_CustomVerificationFailureMessage.java:23)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)

36. Java 8 Lambda Matcher Support (Since 2.1.0)

通常使用 Java 8 lambda 表达式和 ArgumentMatcher 来减少对 ArgumentCaptor 的依赖。但验证对模拟对象上的函数调用的输入是否正确,通常会使用 ArgumentCaptor 来找到使用的操作数,然后对它们进行后续的断言。对于复杂的示例,这样做可能很有用,但也很冗长。
编写一个 lambda 表达式来表达匹配是相当简单的。当你的函数的参数与 argThat 结合使用时,它将作为强类型对象传递给 ArgumentMatcher,可以对其进行任何操作。

@Test
void action036_001() {
    List<String> list = mock(ArrayList.class);

    list.add("1234");
    list.add("2345");
    list.add("34567");

    // org.mockito.ArgumentMatchers#argThat()
    // verify a list only had strings of a certain length added to it
    // note - this will only compile under Java 8
    verify(list, times(2)).add(argThat(string -> string.length() < 5));

    // Java 7 equivalent - not as neat
    verify(list, times(2)).add(argThat(new ArgumentMatcher<String>() {
        public boolean matches(String arg) {
            return arg.length() < 5;
        }
    }));
}

public class ComplexObject {
    private List<String> subObject;

    public ComplexObject(List<String> subObject) {
        this.subObject = subObject;
    }

    public List<String> getSubObject() {
        return subObject;
    }
}

interface Target {
    void receiveComplexObject(ComplexObject obj);
}

public class MockClass {
    public String someMethod(List<?> list) {
        return list.size() < 3 ? "Mocked value" : null;
    }
}

@Test
void action036_002() {
    Target target = mock(Target.class);
    ComplexObject complexObject = new ComplexObject(Arrays.asList("expected", "other"));

    // Call some method on the target with the complex object
    target.receiveComplexObject(complexObject);

    // more complex Java 8 example - where you can specify complex verification behaviour functionally
    verify(target, times(1)).receiveComplexObject(argThat(obj -> obj.getSubObject().get(0).equals("expected")));

    MockClass mock = mock(MockClass.class);

    // Define the behavior of the mock 'mock' for the 'someMethod' method
    // If the input list has fewer than 3 items, return "Mocked value"
    when(mock.someMethod(argThat(list -> list.size() < 3))).thenReturn("Mocked value");


    // Test the behavior of the mock
    List<String> inputList1 = Arrays.asList("item1", "item2");
    List<String> inputList2 = Arrays.asList("item1", "item2", "item3");

    // org.junit.jupiter.api.Assertions#assertEquals
    // org.junit.jupiter.api.Assertions#assertNull
    assertEquals("Mocked value", mock.someMethod(inputList1));
    assertNull(mock.someMethod(inputList2));
}

37. Java 8 Custom Answer Support (Since 2.1.0)

class PersonService {
    public String getSerialNo() {
        return UUID.randomUUID().toString();
    }
    public int guessAge(String firstName, String lastName, String gender) {
        return 0;
    }
}

@Test
void action037_001() {
    PersonService personService = mock(PersonService.class);

    // answer by returning Iny every time
    doAnswer(invocation -> "Iny").when(personService).getSerialNo();
    assertEquals("Iny", personService.getSerialNo());

    // answer by using one of the parameters - converting into the right
    // type as your go - in this case, returning the length of the second string parameter
    // as the answer. This gets long-winded quickly, with casting of parameters.
    doAnswer(invocation -> ((String)invocation.getArgument(0)).length())
            .when(personService).guessAge(anyString(), anyString(), anyString());
    assertEquals(8, personService.guessAge("Fernando", "Shui", "Male"));
}

public interface Callback {
	void receive(String item);
}

public interface ExampleInterface {
    // Example interface to be mocked has a function like:
    void execute(String operand, Callback callback);
    // returning a value is possible with the answer() function
    // and the non-void version of the functional interfaces
    // so if the mock interface had a method like
    boolean isSameString(String input1, String input2);
}

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

@Test
void action037_002() {
    ExampleInterface mock = mock(ExampleInterface.class);

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

    doAnswer(answerVoid(M37_Java8CustomAnswer::dummyCallbackImpl))
            .when(mock).execute(anyString(), any(Callback.class));

    // org.mockito.stubbing.VoidAnswer2
    // Java 7
    doAnswer(answerVoid(new VoidAnswer2<String, Callback>() {
        public void answer(String operation, Callback callback) {
            callback.receive("dummy");
        }})).when(mock).execute(anyString(), any(Callback.class));

    // this could be mocked
    // Java 8
    doAnswer(AdditionalAnswers.<Boolean,String,String>answer((input1, input2) -> input1.equals(input2)))
            .when(mock).execute(anyString(), any(Callback.class));

    // Java 7
    doAnswer(answer(new Answer2<String, String, String>() {
        public String answer(String input1, String input2) {
            return input1 + input2;
        }})).when(mock).execute(anyString(), any(Callback.class));
}

38. Meta data and generic type retention (Since 2.1.0)

@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {}

@MyAnnotation
class Foo {
    List<String> bar() { return null; }
}

@Test
void action038() {
    MockSettings settings = Mockito.withSettings().defaultAnswer(Mockito.RETURNS_DEFAULTS);
    Class<?> mockType = Mockito.mock(Foo.class, settings).getClass();
    assertTrue(mockType.isAnnotationPresent(MyAnnotation.class));

    try {
        Method method = mockType.getDeclaredMethod("bar");
        assertTrue(method.getGenericReturnType() instanceof ParameterizedType);
        System.out.println("All assertions passed successfully.");
    } catch (AssertionError | NoSuchMethodException e) {
        System.out.println("Assertion failed: " + e.getMessage());
    }
}

39. Mocking final types, enums and final methods (Since 2.1.0)

final class FinalClass {
    public String finalMethod() {
        return "Original result";
    }
    Status status = Status.DEFAULT;
    public Status getStatus() {
        return status;
    }
}

enum Status {
    SUCCESS,
    FAILURE,
    DEFAULT;

    public String getName() {
        return name();
    }
}

@Test
void action039() {
    FinalClass mockFinalClass = mock(FinalClass.class);
    when(mockFinalClass.finalMethod()).thenReturn("Mocked result");
    assertEquals("Mocked result", mockFinalClass.finalMethod());

    when(mockFinalClass.getStatus()).thenReturn(Status.FAILURE);
    assertEquals(Status.FAILURE, mockFinalClass.getStatus());

    Status status = mock(Status.class);
    when (status.getName()).thenReturn("UNKNOWN");
    assertEquals("UNKNOWN", status.getName());
}

45. New JUnit Jupiter (JUnit5+) extension

org.mockito:mockito-junit-jupiter 与JUnit Jupiter(JUnit5+)集成。

48. Mocking static methods (since 3.4.0)

在使用inline mock maker时,可以在当前线程和用户定义的范围内模拟静态方法调用。此时,Mockito 确保并发和顺序运行的测试不会相互影响。为了确保静态模拟的临时性,建议在 try-with-resources 结构中定义范围。

class Dog {
    public static String bark() {
        return "wang wang";
    }
}

@Test
void action048() {
    assertEquals("wang wang", Dog.bark());

    try (MockedStatic mockedStatic = mockStatic(Dog.class)) {
        mockedStatic.when(Dog::bark).thenReturn("miao");
        assertEquals("miao", Dog.bark());
        mockedStatic.verify(Dog::bark);
    }

    assertEquals("wang wang", Dog.bark());
}

49. Mocking object construction (since 3.5.0)

在使用inline mock maker时,可以在当前线程和用户定义的范围内生成构造函数调用的模拟。此时,Mockito 确保并发和顺序运行的测试不会相互影响。为了确保静态模拟的临时性,建议在 try-with-resources 结构中定义范围。

class Dog {
    public String bark() {
        return "wang wang";
    }
}

@Test
void action048() {
    assertEquals("wang wang", new Dog().bark());

    try (MockedConstruction mockedConstruction = mockConstruction(Dog.class)) {
        Dog dog = new Dog();
        when(dog.bark()).thenReturn("miao");
        assertEquals("miao", dog.bark());
        verify(dog).bark();
    }

    assertEquals("wang wang", new Dog().bark());
}

51. Mark classes as unmockable (since 4.1.0)

@DoNotMock
class Person {
    public String getName() {
        return "name";
    }
}

@Test
void action051() {
    // org.mockito.exceptions.misusing.DoNotMockException: 
    // class pr.iceworld.fernando.M51_MarkClassesAsUnmockable$Person
    // is annotated with @org.mockito.DoNotMock and can't be mocked.
    // Create a real instance instead.
    // Person person = mock(Person.class);
    // Person person = spy(Person.class);
    Person person = new Person();
}

54. Mocking/spying without specifying class (Since 4.10.0)

通常可以调用不带参数的方法mock()或spy(),而不是使用Class参数调用方法mock(Class)或spy(Class):

public abstract class AbsPerson {
    public AbsPerson() {
    }
    public abstract String getFirstName();

    public abstract String getLastName();
}
public class Person extends AbsPerson {

    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        LastName = lastName;
    }

    public Person() {
    }

    private String firstName;
    private String LastName = "Shui";
    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return LastName;
    }

    public String characteristics() {
        return "Normal";
    }

    public String getIdCard() {
        return "";
    }
}
public class M54_MockingOrSpyWithoutSpecifyingClass {
    @Test
    void action054_001() {
        ArrayList list = spy();
        list.add("1");
        assertEquals(1, list.size());
    }

    @Test
    void action054_002() {
        Person person = mock();
        when(person.getFirstName()).thenReturn("X");
        assertEquals("X", person.getFirstName());
        // spy issue here

        Person instance = new Person();
        Person personSpy = spy(instance);
        assertEquals("Shui", personSpy.getLastName());
		
		// ensure the AbsPerson class and Person class is in a single java file
		// otherwise this would fail with message `Please ensure that the target class has a 0-arg constructor.`, even there were a 0-arg constructor, it would fail also
        AbsPerson absPersonSpy = spy();
        assertNull(absPersonSpy.getLastName());
        absPersonSpy = spy(AbsPerson.class);
        assertNull(absPersonSpy.getLastName());
        
		personSpy = spy();
        assertEquals("Shui", personSpy.getLastName());
        personSpy = spy(Person.class);
        assertEquals("Shui", personSpy.getLastName());
    }
}

55. Verification with assertions (Since 5.3.0)

class PersonService {
    public void updateName(Person person) {
        // some implementation
    }
}

class Person {
    private String firstName;
    private String LastName;

    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        LastName = lastName;
    }
    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return LastName;
    }
}

@Test
void action055() {
    PersonService personService = mock(PersonService.class);

    Person person = new Person("Fernando", "Shui");
    personService.updateName(person);

    verify(personService).updateName(assertArg(param -> {
        assertEquals("Fernando", param.getFirstName());
        assertEquals("Shui", param.getLastName());
    }));
}

Source Code

https://github.com/fernado/mockito-demo.git

Reference

https://javadoc.io/doc/org.mockito/mockito-core/5.11.0/org/mockito/Mockito.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值