Mockito與PowerMock都是Java流行的一種Mock框架,使用Mock技術能讓咱們隔離外部依賴以便對咱們本身的業務邏輯代碼進行單元測試,在編寫單元測試時,不須要再進行繁瑣的初始化工做,在須要調用某一個接口時,直接模擬一個假方法,並任意指定方法的返回值。
Mockito的工做原理是經過建立依賴對象的proxy,全部的調用先通過proxy對象,proxy對象攔截了全部的請求再根據預設的返回值進行處理。PowerMock則在Mockito原有的基礎上作了擴展,經過修改類字節碼並使用自定義ClassLoader加載運行的方式來實現mock靜態方法、final方法、private方法、系統類的功能。
從二者的項目結構中就能夠看出,PowerMock直接依賴於Mockito,因此若是項目中已經導入了PowerMock包就不須要再單獨導入Mockito包,若是二者同時導入還要當心PowerMock和Mockito不一樣版本之間的兼容問題:java
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>2.23.0</version> <scope>test</scope> </dependency>
<dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>2.0.0-RC.3</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito2</artifactId> <version>2.0.0-RC.3</version> <scope>test</scope> </dependency>
Mockito通常經過建立mock或spy對象,並制定具體返回規則來實現模擬的功能,在調用完成後還能夠進行方法調用驗證以檢驗程序邏輯是否正確。mock和spy對象的區別是mock對象對於未指定處理規則的調用會按方法返回值類型返回該類型的默認值(如int、long則返回0,boolean則返回false,對象則返回null,void則什麼都不作),而spy對象在未指定處理規則時則會直接調用真實方法。node
如下3個類是咱們的項目中須要用到的一些業務類:git
//實體類 public class Node { private int num; private String name; //如下忽略全部構造方法和get、set方法 } //本地負責實現具體業務的業務類 public class LocalServiceImpl implements ILocalService { //外部依賴 @Autowired private IRemoteService remoteService; //具體業務處理方法 @Override public Node getRemoteNode(int num) { return remoteService.getRemoteNode(num); } //如下忽略其餘業務調用方法,在後面例子中補充 } //外部依賴業務類,由其餘人實現,可能咱們的業務類寫好了別人還沒寫好 public class RemoteServiceImpl implements IRemoteService { //外部類提供的一些業務方法 @Override public Node getRemoteNode(int num) { return new Node(num, "Node from remote service"); } //其餘業務方法在後面例子中補充 }
下面是Mockito具體使用的一些示例:github
@RunWith(MockitoJUnitRunner.class) //讓測試運行於Mockito環境 public class LocalServiceImplMockTest { @InjectMocks //此註解表示這個對象須要被注入mock對象 private LocalServiceImpl localService; @Mock //此註解會自動建立1個mock對象並注入到@InjectMocks對象中 private RemoteServiceImpl remoteService; //若是不使用上述註解,可使用@Before方法來手動進行mock對象的建立和注入,但會幾行不少代碼 /* private LocalServiceImpl localService; private RemoteServiceImpl remoteService; @Before public void setUp() throws Exception { localService = new LocalServiceImpl(); remoteService = Mockito.mock(RemoteServiceImpl.class); //建立Mock對象 Whitebox.setInternalState(localService, "remoteService", remoteService); //注入依賴對象 } */ @Test public void testMock() { Node target = new Node(1, "target"); //建立一個Node對象做爲返回值 Mockito.when(remoteService.getRemoteNode(1)).thenReturn(target); //指定當remoteService.getRemoteNode(int)方法傳入參數爲1時返回target對象 Node result = localService.getRemoteNode(1); //調用咱們的業務方法,業務方法內部調用依賴對象方法 assertEquals(target, result); //能夠斷言咱們獲得的返回值其實就是target對象 assertEquals(1, result.getNum()); //具體屬性和咱們指定的返回值相同 assertEquals("target", result.getName()); //具體屬性和咱們指定的返回值相同 Node result2 = localService.getRemoteNode(2); //未指定參數爲2時對應的返回規則 assertNull(result2); //未指定時返回爲null } }
@RunWith(MockitoJUnitRunner.class) public class LocalServiceImplSpyTest { @InjectMocks private LocalServiceImpl localService; @Spy //注意這裏使用的是@Spy註解 private RemoteServiceImpl remoteService; //注意若是本身建立spy對象的話要這麼寫: /* remoteService = new RemoteServiceImpl(); //先建立一個具體實例 remoteService = Mockito.spy(remoteService); //再調用Mockito.spy(T)方法建立spy對象 */ @Test public void testSpy() { Node target = new Node(1, "target"); //建立一個Node對象做爲返回值 Mockito.when(remoteService.getRemoteNode(1)).thenReturn(target); //指定當remoteService.getRemoteNode(int)方法傳入參數爲1時返回target對象 Node result = localService.getRemoteNode(1); //調用咱們的業務方法,業務方法內部調用依賴對象方法 assertEquals(target, result); //能夠斷言咱們獲得的返回值其實就是target對象 assertEquals(1, result.getNum()); //具體屬性和咱們指定的返回值相同 assertEquals("target", result.getName()); //具體屬性和咱們指定的返回值相同 Node result2 = localService.getRemoteNode(2); //未指定參數爲2時的調用規則,因此會直接調用真實對象,返回remote建立的節點 assertEquals(2, result2.getNum()); assertEquals("Node from remote service", result2.getName()); //remoteService建立Node對象時設置name屬性爲"Node from remote service" } }
@Test public void testAny() { Node target = new Node(1, "target"); when(remoteService.getRemoteNode(anyInt())).thenReturn(target); //靜態導入Mockito.when和ArgumentMatchers.anyInt後能夠簡化代碼提高可讀性 Node result = localService.getRemoteNode(20); //上面指定了調用remoteService.getRemoteNode(int)時,無論傳入什麼參數都會返回target對象 assertEquals(target, result); //能夠斷言咱們獲得的返回值其實就是target對象 assertEquals(1, result.getNum()); //具體屬性和咱們指定的返回值相同 assertEquals("target", result.getName()); //具體屬性和咱們指定的返回值相同 }
/** * 指定mock屢次調用返回值 */ @Test public void testMultipleReturn() { Node target1 = new Node(1, "target"); Node target2 = new Node(1, "target"); Node target3 = new Node(1, "target"); when(remoteService.getRemoteNode(anyInt())).thenReturn(target1).thenReturn(target2).thenReturn(target3); //第一次調用返回target一、第二次返回target二、第三次返回target3 Node result1 = localService.getRemoteNode(1); //第1次調用 assertEquals(target1, result1); Node result2 = localService.getRemoteNode(2); //第2次調用 assertEquals(target2, result2); Node result3 = localService.getRemoteNode(3); //第3次調用 assertEquals(target3, result3); }
//RemoteServiceImpl方法: @Override public Node getRemoteNode(String name) throws MockException { if (StringUtils.isEmpty(name)) { throw new MockException("name不能爲空", name); } return new Node(name); } //LocalServiceImpl方法 @Override public Node getRemoteNode(String name) throws MockException { try { return remoteService.getRemoteNode(name); } catch (IllegalArgumentException e) { throw e; } } /** * 指定mock對象已聲明異常拋出的方法拋出受檢查異常 */ @Test public void testExceptionDeclare() { try { Node target = new Node(1, "target"); when(remoteService.getRemoteNode("name")).thenReturn(target).thenThrow(new MockException( "message", "exception")); //第一次調用正常返回,第二次則拋出一個Exception Node result1 = localService.getRemoteNode("name"); assertEquals(target, result1); //第一次調用正常返回 Node result2 = localService.getRemoteNode("name"); //第二次調用不會正常返回,會拋出異常 assertEquals(target, result2); } catch (MockException e) { assertEquals("exception", e.getName()); //驗證是否返回指定異常內容 assertEquals("message", e.getMessage()); //驗證是否返回指定異常內容 } } /** * 指定mock對象爲聲明異常拋出的方法拋出運行時異常 */ @Test public void testRuntimeException() { Node target = new Node(1, "target"); when(remoteService.getRemoteNode(1)).thenThrow(new RuntimeException("exception")); //指定調用時拋出一個運行時異常 try { Node result = localService.getRemoteNode(1); assertEquals(target, result); } catch (RuntimeException e) { assertEquals("exception", e.getMessage()); } } /** * 指定mock對象未聲明異常拋出的方法拋出受檢查異常,如下方法執行會報錯 */ @Test public void testNotDefineCheckedException() { Node target = new Node(1, "target"); when(remoteService.getRemoteNode(1)).thenThrow(new IOException("io exception")); try { Node result = localService.getRemoteNode(1); assertEquals(target, result); } catch (Exception e) { assertEquals("io exception", e.getMessage()); } }
//RemoteServiceImpl方法: @Override public void doSometing() { System.out.println("remote service do something!"); } //LocalServiceImpl方法 @Override public void remoteDoSomething() { remoteService.doSometing(); } //注意void方法沒有返回值,因此mock規則寫法順序不同 doNothing().when(remoteService).doSometing(); doThrow(new RuntimeException("exception")).when(remoteService).doSometing();
/** * 校驗mock對象和方法的調用狀況 * */ public void testVerify() { Node target = new Node(1, "target"); when(remoteService.getRemoteNode(anyInt())).thenReturn(target); verify(remoteService, never()).getRemoteNode(1); //mock方法未調用過 localService.getRemoteNode(1); Mockito.verify(remoteService, times(1)).getRemoteNode(anyInt()); //目前mock方法調用過1次 localService.getRemoteNode(2); verify(remoteService, times(2)).getRemoteNode(anyInt()); //目前mock方法調用過2次 verify(remoteService, times(1)).getRemoteNode(2); //目前mock方法參數爲2只調用過1次 }
/** * 利用ArgumentCaptor捕獲方法參數進行mock方法參數校驗 */ @Test public void testCaptor() throws Exception { Node target = new Node(1, "target"); when(remoteService.getRemoteNode(anyString())).thenReturn(target); localService.getRemoteNode("name1"); localService.getRemoteNode("name2"); verify(remoteService, atLeastOnce()).getRemoteNode(localCaptor.capture()); //設置captor assertEquals("name2", localCaptor.getValue()); //獲取最後一次調用的參數 List<String> list = localCaptor.getAllValues(); //按順序獲取全部傳入的參數 assertEquals("name1", list.get(0)); assertEquals("name2", list.get(1)); }
/** * mock對象調用真實方法 */ @Test public void testCallRealMethod() { when(remoteService.getRemoteNode(anyInt())).thenCallRealMethod(); //設置調用真實方法 Node result = localService.getRemoteNode(1); assertEquals(1, result.getNum()); assertEquals("Node from remote service", result.getName()); }
//重置mock,清除全部的調用記錄和返回規則 Mockito.reset(remoteService);
/** * 校驗mock對象0調用和未被驗證的調用 */ @Test(expected = NoInteractionsWanted.class) public void testInteraction() { verifyZeroInteractions(remoteService); //目前還未被調用過,執行不報錯 Node target = new Node(1, "target"); when(remoteService.getRemoteNode(anyInt())).thenReturn(target); localService.getRemoteNode(1); localService.getRemoteNode(2); verify(remoteService, times(2)).getRemoteNode(anyInt()); // 參數1和2的兩次調用都會被上面的anyInt()校驗到,因此沒有未被校驗的調用了 verifyNoMoreInteractions(remoteService); reset(remoteService); localService.getRemoteNode(1); localService.getRemoteNode(2); verify(remoteService, times(1)).getRemoteNode(1); // 參數2的調用不會被上面的校驗到,因此執行會拋異常 verifyNoMoreInteractions(remoteService); }
PowerMock的使用與Mockito有一些不一樣,首先是測試類上的@RunWith註解須要修改成:api
@RunWith(PowerMockRunner.class)
第二是須要使用到@PrepareForTest註解(PrepareFotTest註解會修改傳入參數類的字節碼,經過修改字節碼達到模擬final、static、私有方法、系統類等的功能),此註解可寫在類上也可寫在方法上:框架
@PrepareForTest(RemoteServiceImpl.class)
//LocalServiceImpl @Override public Node getLocalNode(int num, String name) { return new Node(num, name); } /** * mock new關鍵字 */ @Test @PrepareForTest(LocalServiceImpl.class) //PrepareForTest修改local類的字節碼以覆蓋new的功能 public void testNew() throws Exception { Node target = new Node(1, "target"); //當傳入任意int且name屬性爲"name"時,new對象返回爲target //當參數條件使用了any系列方法時,剩餘的參數都得使用相應的模糊匹配規則,如eq("name")表明參數等於"name" //剩餘還有isNull(), isNotNull(), isA()等方法 PowerMockito.whenNew(Node.class).withArguments(anyInt(), eq("name")).thenReturn(target); Node result = localService.getLocalNode(2, "name"); assertEquals(target, result); //返回值爲target assertEquals(1, result.getNum()); assertEquals("target", result.getName()); //未指定name爲"test"的返回值,默認返回null Node result2 = localService.getLocalNode(1, "test"); assertNull(result2); }
//RemoteServiceImpl @Override public final Node getFinalNode() { return new Node(1, "final node"); } /** * mock final方法 */ @Test @PrepareForTest(RemoteServiceImpl.class) //final方法在RemoteServiceImpl類中 public void testFinal() { Node target = new Node(2, "mock"); PowerMockito.when(remoteService.getFinalNode()).thenReturn(target); //指定返回值 Node result = remoteService.getFinalNode(); //直接調用final方法,返回mock後的值 assertEquals(target, result); //驗證返回值 assertEquals(2, result.getNum()); assertEquals("mock", result.getName()); }
//Node public static Node getStaticNode() { return new Node(1, "static node"); } /** * mock static方法 */ @Test @PrepareForTest(Node.class) //static方法定義在Node類中 public void testStatic() { Node target = new Node(2, "mock"); PowerMockito.mockStatic(Node.class); //mock static方法前須要加這一句 PowerMockito.when(Node.getStaticNode()).thenReturn(target); //指定返回值 Node result = Node.getStaticNode(); //直接調用static方法,返回mock後的值 assertEquals(target, result); //驗證返回值 assertEquals(2, result.getNum()); assertEquals("mock", result.getName()); }
//RemoteServiceImpl @Override public Node getPrivateNode() { return privateMethod(); } //RemoteServiceImpl private Node privateMethod() { return new Node(1, "private node"); } /** * mock 私有方法 */ @Test @PrepareForTest(RemoteServiceImpl.class) //private方法定義在RemoteServiceImpl類中 public void testPrivate() throws Exception { Node target = new Node(2, "mock"); //按照真實代碼調用privateMethod方法 PowerMockito.when(remoteService.getPrivateNode()).thenCallRealMethod(); //私有方法沒法訪問,相似反射傳遞方法名和參數,此處無參數故未傳 PowerMockito.when(remoteService, "privateMethod").thenReturn(target); Node result = remoteService.getPrivateNode(); assertEquals(target, result); //驗證返回值 assertEquals(2, result.getNum()); assertEquals("mock", result.getName()); }
//RemoteServiceImpl @Override public Node getSystemPropertyNode() { return new Node(System.getProperty("abc")); } /** * mock 系統類方法 */ @Test @PrepareForTest(RemoteServiceImpl.class) //相似new關鍵字,系統類方法的調用在類RemoteServiceImpl中,因此這裏填的是RemoteServiceImpl public void testSystem() { PowerMockito.mockStatic(System.class); //調用的是系統類的靜態方法,因此要加這一句 PowerMockito.when(System.getProperty("abc")).thenReturn("mock"); //設置System.getProperty("abc")返回"mock" PowerMockito.when(remoteService.getSystemPropertyNode()).thenCallRealMethod(); //設置mock對象調用實際方法 Node result = remoteService.getSystemPropertyNode(); //按代碼會返回一個name屬性爲"mock"的對象 assertEquals(0, result.getNum()); //int默認值爲0 assertEquals("mock", result.getName()); //remoteService對象中調用System.getProperty("abc")返回的是上面設置的"mock" }
項目代碼已上傳至GitHub:MockDemomaven