package com.camnter.newlife; import com.camnter.newlife.bean.Contacts; import com.camnter.newlife.bean.Tag; import java.util.LinkedList; import java.util.List; import junit.framework.TestCase; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.atMost; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; /** * Description:MockitoTest * Created by:CaMnter */ public class MockitoTest extends TestCase { private static final String MOCK_TEXT = "CaMnter"; /** * 1. 让我们验证某些行为 * 一旦创建,mock对象会记住所有的交互。然后你可以有选择性的验证你感兴趣的任何交互。 */ @SuppressWarnings("unchecked") public void test1() { System.out.println("\nMockitoTest >>>>>> [test1] >>>>>>"); // 创建mock List mockedList = mock(List.class); // 使用 mock 对象 mockedList.add("one"); mockedList.clear(); // 验证 verify(mockedList).add("one"); verify(mockedList).clear(); } /** * 2. 再来一点 stubbing? * * 默认情况, 对于返回一个值的所有方法, mock 对象在适当的时候要不返回 null,基本类型/基本类型包装类,或 * 者一个空集合。比如 int/Integer 返回0, boolean/Boolean 返回 false。 * * 存根 ( stub ) 可以覆盖: 例如通用存根可以固定搭建但是测试方法可以覆盖它。请注意覆盖存根是潜在的代码异 * 味( code smell ),说明存根太多了 * * 一旦做了存根,方法将总是返回存根的值,无论这个方法被调用多少次 * * 最后一个存根总是更重要 - 当你用同样的参数对同一个方法做了多次存根时。换句话说:存根顺序相关,但是它 * 只在极少情况下有意义。例如,当需要存根精确的方法调用次数,或者使用参数匹配器等。 */ public void test2() { System.out.println("\nMockitoTest >>>>>> [test2] >>>>>>"); // 可以mock具体的类,而不仅仅是接口 LinkedList mockedList = mock(LinkedList.class); // 存根(stubbing) when(mockedList.get(0)).thenReturn("first"); when(mockedList.get(1)).thenThrow(new RuntimeException()); // 下面会打印 "first" System.out.println(mockedList.get(0)); // 下面会抛出运行时异常 try { System.out.println(mockedList.get(1)); } catch (Exception e) { e.printStackTrace(); } // 下面会打印"null" 因为get(999)没有存根(stub) System.out.println(mockedList.get(999)); // 虽然可以验证一个存根的调用,但通常这是多余的 // 如果你的代码关心get(0)返回什么,那么有某些东西会出问题(通常在verify()被调用之前) // 如果你的代码不关系get(0)返回什么,那么它不需要存根。如果不确信,那么还是验证吧 verify(mockedList).get(0); } /** * 3. 参数匹配器 * mockito使用java原生风格来验证参数的值: 使用equals()方法。有些时候,如果需要额外的灵活性,应该使用参数匹配器: */ public void test3() { System.out.println("\nMockitoTest >>>>>> [test3] >>>>>>"); LinkedList mockedList = mock(LinkedList.class); //使用内建anyInt()参数匹配器 when(mockedList.get(anyInt())).thenReturn("element"); //使用自定义匹配器( 这里的 isValid() 返回自己的匹配器实现 ) when(mockedList.contains(argThat(null))).thenReturn(false); //下面会打印 "element" System.out.println(mockedList.get(999)); // 同样可以用参数匹配器做验证 verify(mockedList).get(anyInt()); } /** * 4. 验证精确调用次数/至少X次/从不 */ @SuppressWarnings("unchecked") public void test4() { System.out.println("\nMockitoTest >>>>>> [test4] >>>>>>"); LinkedList mockedList = mock(LinkedList.class); // 使用 mock mockedList.add("once"); mockedList.add("twice"); mockedList.add("twice"); mockedList.add("three times"); mockedList.add("three times"); mockedList.add("three times"); // 下面两个验证是等同的 - 默认使用 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"); //使用using never()来验证. never()相当于 times(0) verify(mockedList, never()).add("never happened"); //使用 atLeast()/atMost()来验证 verify(mockedList, atLeastOnce()).add("three times"); verify(mockedList, atMost(5)).add("three times"); } /** * 5. 使用 exception 做 void 方法的存根 */ public void test5() { System.out.println("\nMockitoTest >>>>>> [test5] >>>>>>"); LinkedList mockedList = mock(LinkedList.class); doThrow(new RuntimeException()).when(mockedList).clear(); // 下面会抛出 RuntimeException: try { mockedList.clear(); } catch (Exception e) { e.printStackTrace(); } } /** * 6. 验证顺序 */ @SuppressWarnings("unchecked") public void test6() { // A. 单个 Mock,方法必须以特定顺序调用 List singleMock = mock(List.class); // 使用单个 Mock singleMock.add("was added first"); singleMock.add("was added second"); // 为 singleMock 创建 inOrder 检验器 InOrder inOrder = Mockito.inOrder(singleMock); // 下面将确保 add 方法第一次调用是用 "was added first" ,然后是用 "was added second" inOrder.verify(singleMock).add("was added first"); inOrder.verify(singleMock).add("was added second"); // B. 多个 Mock 必须以特定顺序调用 List firstMock = mock(List.class); List secondMock = mock(List.class); // 使用 mock firstMock.add("was called first"); secondMock.add("was called second"); //创建 inOrder 对象,传递任意多个需要验证顺序的 mock inOrder = Mockito.inOrder(firstMock, secondMock); // 下面将确保 firstMock 在 secondMock 之前调用 inOrder.verify(firstMock).add("was called first"); inOrder.verify(secondMock).add("was called second"); // Oh, 另外 A + B 可以任意混合 } /** * 7. 确保交互从未在mock对象上发生 */ @SuppressWarnings("unchecked") public void test7() { System.out.println("\nMockitoTest >>>>>> [test7] >>>>>>"); LinkedList mockOne = mock(LinkedList.class); LinkedList mockTwo = mock(LinkedList.class); LinkedList mockThree = mock(LinkedList.class); // 使用 mock - 仅有 mockOne 有交互 mockOne.add("one"); // 普通验证 verify(mockOne).add("one"); // 验证方法从未在 mock 对象上调用 verify(mockOne, never()).add("two"); //验证其他mock没有交互 verifyZeroInteractions(mockTwo, mockThree); } /** * 8. 发现冗余调用 * 警告:默写做过很多经典的 expect-run-verify mock 的用户倾向于非常频繁的使用verifyNoMoreInteractions(), * 甚至在每个测试方法中。不推荐在每个测试中都使用verifyNoMoreInteractions()。 * verifyNoMoreInteractions()是交互测试工具集中的便利断言。 * 仅仅在真的有必要时使用。滥用它会导致定义过度缺乏可维护性的测试。可以在这里找到更多阅读内容。 * 可以看 never() - 这个更直白并且将意图交代的更好。 */ @SuppressWarnings("unchecked") public void test8() { System.out.println("\nMockitoTest >>>>>> [test8] >>>>>>"); LinkedList mockedList = mock(LinkedList.class); // 使用 mock mockedList.add("one"); mockedList.add("two"); verify(mockedList).add("one"); // 下面的验证将会失败 //verifyNoMoreInteractions(mockedList); } /** * 9. 创建 mock 的捷径 - @mock 注解 * 最大限度的减少罗嗦的创建mock对象的代码 * 让测试类更加可读 * 让验证错误更加可读因为 field name 被用于标志mock对象 */ @Mock private Contacts contacts; public void test9() { System.out.println("\nMockitoTest >>>>>> [test9] >>>>>>"); // 初始化 有Mock 对象 MockitoAnnotations.initMocks(this); // 初始化 没注解的 对象 Tag tag = mock(Tag.class); assertNotNull(tag); assertNotNull(this.contacts); } /** * 10. 存根连续调用 ( 游历器风格存根 ) * 有时我们需要为同一个方法调用返回不同值/异常的存根。 * 典型使用场景是mock游历器。 * 早期版本的mockito没有这个特性来改进单一模拟。 * 例如,为了替代游历器可以使用Iterable或简单集合。 * 那些可以提供存根的自然方式(例如,使用真实的集合)。在少量场景下存根连续调用是很有用的 */ public void testA0() { System.out.println("\nMockitoTest >>>>>> [testA0] >>>>>>"); Tag tag = mock(Tag.class); tag.setContent("CaMnter"); when(tag.getContent()) .thenThrow(new RuntimeException()) .thenReturn("thenReturn CaMnter"); // 第一次调用:抛出运行时异常 try { tag.getContent(); } catch (Exception e) { e.printStackTrace(); } // 第二次调用: 打印 "thenReturn CaMnter" System.out.println(tag.getContent()); // 任何连续调用: 还是打印 "thenReturn CaMnter" (最后的存根生效). System.out.println(tag.getContent()); } /** * 11. 带回调的存根 * 还有另外一种有争议的特性,最初没有包含的mockito中。推荐简单用 thenReturn() 或者 thenThrow() 来做 * 存根, 这足够用来测试/测试驱动任何干净而简单的代码。然而,如果你对使用一般Answer接口的存根有需要 */ public void testA1() { System.out.println("\nMockitoTest >>>>>> [testA1] >>>>>>"); final Tag tag = mock(Tag.class); tag.setContent("CaMnter"); when(tag.getContent()).thenAnswer(new Answer<String>() { @Override public String answer(InvocationOnMock invocation) throws Throwable { Object[] args = invocation.getArguments(); Object mock = invocation.getMock(); return "called with method: " + invocation.getMethod().getName(); } }); // 下面会 "called with method: getContent" System.out.println(tag.getContent()); } /** * 12. doReturn() | doThrow() | doAnswer() | doNothing() | doCallRealMethod() 方法家族 * 存根void方法需要when(Object)之外的另一个方式,因为编译器不喜欢括号内的void方法...... * doThrow(Throwable...) 替代 stubVoid(Object) 方法来存根void. 主要原因是改善和doAnswer()方法的可读性和一致性。 * * 可以使用 doThrow(), doAnswer(), doNothing(), doReturn() 和 doCallRealMethod() 代替响应的使用 * when()的调用, 用于任何方法。下列情况是必须的 * 1. 存根void方法 * 2. 在spy对象上存根方法 (看下面) * 3. 多次存根相同方法, 在测试中间改变mock的行为 * 如果喜欢可以用这些方法代替响应的 when(),用于所有存根调用。 */ public void testA2() { System.out.println("\nMockitoTest >>>>>> [testA2] >>>>>>"); LinkedList mockedList = mock(LinkedList.class); doThrow(new RuntimeException("testA2 RuntimeException")).when(mockedList).clear(); try { mockedList.clear(); } catch (Exception e) { e.printStackTrace(); } } /** * Spy 与 Mock 的区别: * List list = new LinkedList(); * List spy = spy(list); * * LinkedList mockedList = mock(LinkedList.class); * * 结论: spy 模拟和真实创建的对象 List。 * * 可以创建实际对象的间谍 (spy)。当使用 spy 时,真实方法被调用(除非方法被存根)。 * 只能小心而偶尔的使用 spy,例如处理遗留代码。 * 在真实对象上做 spy 可以和"部分模拟"的概念关联起来。在1.8版本之前, mockito spy 不是真实的部分模拟。 * 理由是我们觉得部分 mock 是代码异味。 */ @SuppressWarnings("unchecked") public void testA3() { System.out.println("\nMockitoTest >>>>>> [testA3] >>>>>>"); 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"); } /** * Spy实际对象时的重要提示! * * 1. 有时使用when(Object) 来做spy的存根是不可能或者行不通的。在这种情况下使用spy请考虑 * doReturn|Answer|Throw() 方法家族来做存根。例如: * 2. mockito 不会 将调用代理给被传递进去的实际实例,取而代之的是创建它的一个拷贝。因此如 * 果你持有真实实例并和它交互,不要期待spy会感知到这些交互和实际实例的状态影响。推论是说,当 * 一个非存根方法在sky上被调用,而不是在真实实例上调用,真实实例不会有任何影响。 * 3. 对final方法保持警惕。mockito不mock final方法,因此底线是:当你在一个真实对象上 spy + 你想存根 * 一个final方法 = 问题。同样也无法验证这些方法。 */ public void testA4() { System.out.println("\nMockitoTest >>>>>> [testA4] >>>>>>"); List list = new LinkedList(); List spy = spy(list); //不可能: 真实方法被调用因此 spy.get(0) 会抛出IndexOutOfBoundsException (列表现在还是空的) //when(spy.get(0)).thenReturn("foo"); //可以使用 doReturn() 来做存根 doReturn("foo").when(spy).get(0); } /** * 15. 为进一步断言捕获参数 (Since 1.8.0) * mockito 用自然 java 风格验证参数的值:使用 equals() 方法。同样这也是推荐的参数匹配的方式因为它使得 * 测试干净而简单。但是在某些情况下,在实际验证之后对特定参数做断言是很有用的。 * * 警告:推荐在验证时使用ArgumentCaptor,而不是存根。在存根时使用ArgumentCaptor会减少测试的可读性,因 * 为捕获器是在断言(验证或者'then')块的外面创建。也可能会降低defect localization(?)因为如果存根方法没 * 有被调用那么就没有参数被捕获。 * 某种程度上,ArgumentCaptor 和自定义参数匹配器有关联。这两个技术都被用于确认传递给mock的特定参数。但 * 是,ArgumentCaptor在下列情况下可能会更适合些: */ public void testA5() { System.out.println("\nMockitoTest >>>>>> [testA5] >>>>>>"); Contacts mock = mock(Contacts.class); ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class); mock.setHeader("Y"); // 参数捕获 verify(mock).setHeader(argument.capture()); // 使用 equals 断言 assertEquals("Y", argument.getValue()); } /** * 16. 重置mock(Since 1.8.0) */ @SuppressWarnings("unchecked") public void testA6() { List mock = mock(List.class); when(mock.size()).thenReturn(10); mock.add(1); reset(mock); } }