/**
 * @author yigen shi
 * @version 1.0
 * @date 2019/7/13 10:28
 * @description 单元测试之Mockito
 * 官方文档: https://static.javadoc.io/org.mockito/mockito-core/2.23.4/org/mockito/Mockito.html
 */
@SpringBootTest
@RunWith(MockitoJUnitRunner.class)
public class MockitoTest {


  /**
   * 1.让我们验证一些行为! 下面的例子mock一个List,如 add(),get(),clear()方法。 实际上,请不要模拟List类。请改用实例。
   * 一旦创建,模拟将记住所有交互。然后,您可以有选择地验证您感兴趣的任何交互。
   */
  @SuppressWarnings("unchecked")
  @Test
  public void verify_behaviour() {
    //mock creation
    List mockedList = mock(List.class);
    //using mock object
    mockedList.add("one");
    mockedList.clear();
    //verification
    verify(mockedList).add("one");
    verify(mockedList).clear();
  }

  /**
   * 2. 存根 默认情况下,对于返回值的所有方法,mock将根据需要返回null,基元/原始包装器值或空集合。 例如0表示int / Integer,false表示布尔值/布尔值。
   * 可以覆盖Stubbing:例如,常见的存根可以进入夹具设置,但测试方法可以覆盖它。请注意, 覆盖存根是一种潜在的代码气味,指出太多的存根
   * 一旦存根,该方法将始终返回一个存根值,无论它被调用多少次。 最后一个存根更重要 - 当您使用相同的参数多次存根相同的方法时。 换句话说:存根的顺序很重要,但它很少有意义,例如,当使用完全相同的方法调用或有时使用参数匹配器时,等等。
   */
  @Test(expected = RuntimeException.class)
  public void when_thenReturn() {
    //You can mock concrete classes, not just interfaces
    LinkedList mockedList = mock(LinkedList.class);

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

    //following prints "first"
    assertEquals("first", mockedList.get(0));

    //following throws runtime exception
    mockedList.get(1);

    //following prints "null" because get(999) was not stubbed
    assertEquals(null, mockedList.get(999));

    //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. Not convinced? See here.
    verify(mockedList).get(0);
  }

  /**
   * 3. 论证匹配 Mockito使用自然java风格验证参数值:使用equals()方法。有时,当需要额外的灵活性时,您可以使用参数匹配器:
   */


  @Mock
  private List<String> stringList;

  @Test
  public void testVerity() {
    //stubbing using built-in anyInt() argument matcher
    when(stringList.get(anyInt())).thenReturn("element");

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

    //you can also verify using an argument matcher
    verify(stringList).get(anyInt());

  }

  /**
   * 4.验证确切的调用次数 / 至少x /从不 times(1)是默认值。因此,可以省略明确使用时间(1)。
   */
  @SuppressWarnings("unchecked")
  @Test
  public void testVerifyTimes() {
    List mockedList = mock(List.class);
    //using mock
    mockedList.add("once");

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

    mockedList.add("three times");
    mockedList.add("three times");
    mockedList.add("three 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");

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

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


  @Mock
  private List mockedList;

  /**
   * 5. 使用异常对void方法进行存根
   */
  @Test(expected = RuntimeException.class)
  public void testMethodException() {
    doThrow(new RuntimeException()).when(mockedList).clear();

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

  }

  /**
   * 6. 按顺序验证 按顺序进行验证是灵活的 - 您不必逐个验证所有交互,而只需验证您有兴趣按顺序进行测试的那些交互。 此外,您可以创建一个InOrder对象,仅传递与按顺序验证相关的模拟。
   */
  @SuppressWarnings("unchecked")
  @Test
  public void testOrder() {
    // 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");

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

    // 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 inOrder01 = inOrder(firstMock, secondMock);

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

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

  }


  /**
   * 7. 确保在模拟上从未发生过互动
   */
  @SuppressWarnings("unchecked")
  @Test
  public void testTriggered() {
    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");
    List mockTwo = mock(List.class);
    List mockThree = mock(List.class);
    //verify that other mocks were not interacted
    Mockito.verifyZeroInteractions(mockTwo, mockThree);

  }

  /**
   * 8. 查找冗余调用 一句警告:谁做了很多经典的一些用户,期望-运行-验证嘲讽倾向于使用verifyNoMoreInteractions()非常频繁, 甚至在每个测试方法。
   * verifyNoMoreInteractions()不建议在每种测试方法中使用。 verifyNoMoreInteractions()是交互测试工具包中的一个方便的断言。
   * 仅在相关时使用它。滥用它会导致过度指定,不易维护的测试。你可以在这里找到进一步阅读 。
   */
  @SuppressWarnings("unchecked")
  @Test(expected = NoInteractionsWanted.class)
  public void testRedundancy() {
    //using mocks
    mockedList.add("one");
    mockedList.add("two");

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

    //following verification will fail
    Mockito.verifyNoMoreInteractions(mockedList);
  }

  /**
   * 9. 模拟创建的简写 - @Mock注解
   * 最小化重复的模拟创建代码。
   * 使测试类更具可读性。
   * 使验证错误更容易阅读,因为字段名称 用于标识模拟。
   */


  /**
   * 10. 连续调用(迭代器式存根) 有时我们需要为同一个方法调用存根不同的返回值/异常。典型的用例可能是模拟迭代器。 Mockito的原始版本没有这个功能来促进简单的mock。例如,代替迭代器,可以使用Iterable或只是集合。
   * 那些提供了自然的存根方式(例如使用真实的集合)。在极少数情况下,存根连续调用可能很有用,但是:
   */
  @Test(expected = RuntimeException.class)
  public void testInvoke() {
    List mockList = mock(List.class);
    when(mockList.get(0)).thenThrow(new RuntimeException());
    when(mockList.get(1)).thenReturn("foo");
    //First call: throws runtime exception:
    mockList.get(0);

    //Second call: prints "foo"
    System.out.println(mockList.get(1));

  }

  /**
   * 11. 使用回调进行存根 允许使用通用Answer接口进行存根。 另一个有争议的特征, 原本没有包含在Mockito中。我们建议使用thenReturn()或
   * 简单地存根thenThrow(), 这应该足以测试/测试任何干净简单的代码。 但是,如果您确实需要使用通用Answer接口存根,这是一个示例:
   */
  @Test
  public void testRollBack() {
    //使用Answer来生成我们我们期望的返回
    Mockito.when(stringList.get(Mockito.anyInt())).thenAnswer(new Answer<Object>() {
      @Override
      public Object answer(InvocationOnMock invocation) throws Throwable {
        Object[] args = invocation.getArguments();
        return "hello world:" + args[0];
      }
    });
    Assert.assertEquals("hello world:0", stringList.get(0));
    Assert.assertEquals("hello world:999", stringList.get(999));
  }

  /**
   * 12. doReturn()| doThrow()| doAnswer()| doNothing()| doCallRealMethod()一系列方法 Stubbing
   * void方法需要一种不同的方法,when(Object)因为编译器不喜欢括号内的void方法... doThrow()当您想要 使用异常存根void方法时使用:
   * 您可以使用doThrow(),doAnswer(),doNothing(),doReturn() 和doCallRealMethod()在地方与调用相应的when(),对于任何方法。你有必要
   *
   * stub void方法 间谍对象的存根方法(见下文) 多次使用相同的方法,在测试过程中更改模拟的行为。 但when()对于所有的存根调用,您可能更愿意使用这些方法代替替代方法。
   * 详细了解这些方法:
   *
   * doReturn(Object)
   *
   * doThrow(Throwable...)
   *
   * doThrow(Class)
   *
   * doAnswer(Answer)
   *
   * doNothing()
   *
   * doCallRealMethod()
   */
  @Test(expected = RuntimeException.class)
  public void testDoThrow() {
    doThrow(new RuntimeException()).when(mockedList).clear();

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

  }

  @Test(expected = IOException.class)
  public void when_thenThrow() throws IOException {
    OutputStream outputStream = Mockito.mock(OutputStream.class);
    OutputStreamWriter writer = new OutputStreamWriter(outputStream);
    //预设当流关闭时抛出异常
    Mockito.doThrow(new IOException()).when(outputStream).close();
    outputStream.close();
  }

  /**
   * 13. 监视真实物体 您可以创建真实对象的间谍。当你使用spy时,会调用真正的方法(除非方法被存根)。 应该谨慎使用真正的间谍,例如在处理遗留代码时。
   *
   * 对真实物体进行间谍活动可以与“部分嘲弄”概念相关联。 在1.8发布之前,Mockito间谍不是真正的部分嘲笑。 原因是我们认为部分模拟是代码气味。在某些时候,我们找到了部分模拟的合法用例
   * (第三方接口,遗留代码的临时重构,完整的文章在 这里)
   */
  @SuppressWarnings("unchecked")
  @Test
  public void testSpy() {
    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");
  }

  /**
   * Mockito 不会将调用委托给传递的实例,而是实际创建它的副本。因此, 如果您保留真实实例并与之交互,请不要指望间谍知道这些交互及其对实际实例状态的影响。推论是, 当一个*
   * unstubbed *方法在spy *上被调用*但*不在真实实例*上时,你将看不到对真实实例的任何影响。 注意最终方法。Mockito不会模拟最终方法,所以最重要的是:
   * 当你监视真实对象时+你试图找到最终方法=麻烦。 此外,您也无法验证这些方法。
   */
  @Test
  public void testSpy01() {
    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);
  }

  /**
   * 模拟重置
   */
  @SuppressWarnings("unchecked")
  @Test
  public void testReset() {
    List mock = mock(List.class);
    when(mock.size()).thenReturn(10);
    mock.add(1);

    reset(mock);
    assertEquals(0, mock.size());
    //at this point the mock forgot any interactions & stubbing
  }

  /**
   * 可序列化模拟
   */
  @Test
  public void testSerializable() {
    List serializableMock = mock(List.class, Mockito.withSettings().serializable());
  }

  /**
   * 21.新的注释:@Captor, @Spy, @InjectMocks(由于1.8.3)
   * 版本1.8.3带来了新的注释,有时可能会有所帮助:
   *
   * @ Captor简化了创建ArgumentCaptor - 当捕获的参数是一个讨厌的泛型类并且你想避免编译器警告时很有用
   * @ Spy- 你可以用它代替spy(Object)。
   * @ InjectMocks- 自动将模拟或间谍字段注入测试对象。
   */

  /**
   * 22. 超时验证(自1.8.5开始) 允许超时验证。它会导致验证等待指定的时间段进行所需的交互,而不是立即失败(如果尚未发生)。可用于并发条件下的测试。
   *
   * 应该很少使用此功能 - 找出测试多线程系统的更好方法。
   *
   * 尚未实现与InOrder验证一起使用。
   */

  private class SomeMethod {

    public void someMethod() {
      try {
        Thread.sleep(1000);
        System.out.println("someMethod");
      } catch (Exception ex) {
        System.out.println("exception");
      }

    }

  }

  /**
   * 23. 自动实例化@Spies, @InjectMocks并构造注入(由于1.9.0)
   * Mockito现在将尝试实例化@ Spy并InjectMocks使用构造函数注入,setter注入或字段注入来实例化@ fields 。
   *
   * 要利用此功能,您需要使用MockitoAnnotations.initMocks(Object),MockitoJUnitRunner 或MockitoRule。
   *
   * 阅读javadoc中有关可用技巧和注入规则的更多信息 InjectMocks
   */
  /**
   * //instead:
   *
   * @Spy BeerDrinker drinker = new BeerDrinker(); //you can write:
   * @Spy BeerDrinker drinker;
   *
   * //same applies to @InjectMocks annotation:
   * @InjectMocks LocalPub;
   */

  @Mock
  private LockInfoDao lockInfoDao;

  @InjectMocks
  private LockInfoServiceImpl lockInfoService;

  @Test
  public void testInjectMocks() {
    LockInfo lockInfo = new LockInfo(1L, "aaa");
    when(lockInfoDao.selectByPrimaryKey(anyInt())).thenReturn(lockInfo);
    LockInfo info = lockInfoService.findLockInfoById(1L);
    System.out.println(info);

  }

  @Test
  public void testInjectMocks02() {
    List<LockInfo> infos = Lists.newArrayList(new LockInfo(1L, "aaa"),
        new LockInfo(2L, "bbb"));
    when(lockInfoDao.selectAll()).thenReturn(infos);
    PageResult<LockInfo> result = lockInfoService.findLockInfoByCondition(1, 10);
    System.out.println(result);
  }


  /**
   * 25. 验证忽略存根(自1.9.0开始) 为了验证,Mockito现在允许忽略存根。配合verifyNoMoreInteractions()或验证时有时很有用inOrder()。
   * 有助于避免对存根调用进行冗余验证 - 通常我们对验证存根不感兴趣。
   *
   * 警告,ignoreStubs()可能导致过度使用verifyNoMoreInteractions(ignoreStubs(...));
   * 请记住,Mockito不建议使用verifyNoMoreInteractions() javadoc中列出的原因轰炸每个测试verifyNoMoreInteractions(Object...)
   */

  private class Foo {

    public void foo() {
      System.out.println("foo");
    }

  }

  private class Bar {

    public void bar() {
      System.out.println("bar");
    }
  }

  @Test
  public void testIgnoreStubs() {
    Foo mock = mock(Foo.class);
    verify(mock).foo();

    Bar mockTwo = mock(Bar.class);
    verify(mockTwo).bar();

    //ignores all stubbed methods:
    //Mockito.verifyNoMoreInteractions(Mockito.ignoreStubs(mock, mockTwo));

    //creates InOrder that will ignore stubbed
    InOrder inOrder = inOrder(Mockito.ignoreStubs(mock, mockTwo));
    inOrder.verify(mock).foo();
    inOrder.verify(mockTwo).bar();
    inOrder.verifyNoMoreInteractions();
  }


  @Mock
  private List<Integer> intList;

  @Test
  public void shorthand() {
    intList.add(1);
    Mockito.verify(intList).add(1);
  }


  /**
   * 参数匹配
   */
  @SuppressWarnings("unchecked")
  @Test
  public void with_arguments() {
    Comparable comparable = Mockito.mock(Comparable.class);
    //预设根据不同的参数返回不同的结果
    Mockito.when(comparable.compareTo("Test")).thenReturn(1);
    Mockito.when(comparable.compareTo("Omg")).thenReturn(2);
    Assert.assertEquals(1, comparable.compareTo("Test"));
    Assert.assertEquals(2, comparable.compareTo("Omg"));
    //对于没有预设的情况会返回默认值
    Assert.assertEquals(0, comparable.compareTo("Not stub"));
  }

  /**
   * 使用方法预期回调接口生成期望值(Answer结构)
   */
  @Test
  public void answer_with_callback() {
    //使用Answer来生成我们我们期望的返回
    Mockito.when(stringList.get(Mockito.anyInt())).thenAnswer(new Answer<Object>() {
      @Override
      public Object answer(InvocationOnMock invocation) throws Throwable {
        Object[] args = invocation.getArguments();
        return "hello world:" + args[0];
      }
    });
    Assert.assertEquals("hello world:0", stringList.get(0));
    Assert.assertEquals("hello world:999", stringList.get(999));
  }

  /**
   * 重置mock对象
   */
  @SuppressWarnings("unchecked")
  @Test
  public void reset_mock() {
    List list = Mockito.mock(List.class);
    Mockito.when(list.size()).thenReturn(10);
    list.add(1);
    Assert.assertEquals(10, list.size());
    //重置mock,清除所有的互动和预设
    Mockito.reset(list);
    Assert.assertEquals(0, list.size());
  }


  /**
   * 验证确切的调用次数
   */
  @SuppressWarnings("unchecked")
  @Test
  public void verifying_number_of_invocations() {
    List list = Mockito.mock(List.class);
    list.add(1);
    list.add(2);
    list.add(2);
    list.add(3);
    list.add(3);
    list.add(3);
    //验证是否被调用一次,等效于下面的times(1)
    Mockito.verify(list).add(1);
    Mockito.verify(list, Mockito.times(1)).add(1);
    //验证是否被调用2次
    Mockito.verify(list, Mockito.times(2)).add(2);
    //验证是否被调用3次
    Mockito.verify(list, Mockito.times(3)).add(3);
    //验证是否从未被调用过
    Mockito.verify(list, Mockito.never()).add(4);
    //验证至少调用一次
    Mockito.verify(list, Mockito.atLeastOnce()).add(1);
    //验证至少调用2次
    Mockito.verify(list, Mockito.atLeast(2)).add(2);
    //验证至多调用3次
    Mockito.verify(list, Mockito.atMost(3)).add(3);
  }


}