Mockito教程

轉載自:https://www.cnblogs.com/Ming8006/p/6297333.htmlhtml

建議閱讀:java

Mockito API:https://static.javadoc.io/org.mockito/mockito-core/2.22.0/org/mockito/Mockito.htmlgit

SpringBoot @MockBean, @SpyBean:https://docs.spring.io/spring-boot/docs/2.0.4.RELEASE/reference/htmlsingle/#boot-features-testing-spring-boot-applications-mocking-beansgithub

 

Mockito教程

2017-01-20spring

目錄api

Mockito 介紹 
  1.1 Mockito是什麼?
  1.2 爲何須要Mock
  1.3 Stub和Mock異同
  1.4 Mockito資源
  1.5 使用場景
使用Mockito 
  2.1 驗證行爲
  2.2 模擬咱們所指望的結果
  2.3 RETURNS_SMART_NULLS和RETURNS_DEEP_STUBS
  2.4 模擬方法體拋出異常
  2.5 使用註解來快速模擬 
  2.6 參數匹配
  2.7 自定義參數匹配
  2.8 捕獲參數來進一步斷言
  2.9 使用方法預期回調接口生成指望值(Answer結構)
  2.10 修改對未預設的調用返回默認指望
  2.11 用spy監控真實對象  
  2.12 真實的部分mock
  2.13 重置mock
  2.14 驗證確切的調用次數
  2.15 連續調用
  2.16 驗證執行順序
  2.17 確保模擬對象上無互動發生
  2.18 找出冗餘的互動(即未被驗證到的)
Mockito如何實現Mock
參考 app

1 Mockito 介紹 [3]


 返回框架

1.1 Mockito是什麼?

Mockito是mocking框架,它讓你用簡潔的API作測試。並且Mockito簡單易學,它可讀性強和驗證語法簡潔。maven

1.2 爲何須要Mock

測試驅動的開發( TDD)要求咱們先寫單元測試,再寫實現代碼。在寫單元測試的過程當中,咱們每每會遇到要測試的類有不少依賴,這些依賴的類/對象/資源又有別的依賴,從而造成一個大的依賴樹,要在單元測試的環境中完整地構建這樣的依賴,是一件很困難的事情。以下圖所示: ide

爲了測試類A,咱們須要Mock B類和C類(用虛擬對象來代替)以下圖所示:

1.3 Stub和Mock異同[1]

  • 相同:Stub和Mock都是模擬外部依賴
  • 不一樣:Stub是徹底模擬一個外部依賴, 而Mock還能夠用來判斷測試經過仍是失敗 

1.4 Mockito資源

1.5 使用場景

  • 提早建立測試; TDD(測試驅動開發)
  • 團隊能夠並行工做
  • 你能夠建立一個驗證或者演示程序
  • 爲沒法訪問的資源編寫測試
  • Mock 能夠交給用戶
  • 隔離系統  

2 使用Mockito [2][4]


 返回

添加maven依賴

      <dependency>
          <groupId>org.mockito</groupId>
          <artifactId>mockito-all</artifactId>
          <version>1.9.5</version>
          <scope>test</scope>
      </dependency>

添加junit依賴

      <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
      </dependency>

添加引用

import static org.mockito.Mockito.*;
import static org.junit.Assert.*;

2.1 驗證行爲

複製代碼
    @Test
    public void verify_behaviour(){
        //模擬建立一個List對象
        List mock = mock(List.class);
        //使用mock的對象
        mock.add(1);
        mock.clear();
        //驗證add(1)和clear()行爲是否發生
        verify(mock).add(1);
        verify(mock).clear();
    }
複製代碼

2.2 模擬咱們所指望的結果

複製代碼
    @Test
    public void when_thenReturn(){
        //mock一個Iterator類
        Iterator iterator = mock(Iterator.class);
        //預設當iterator調用next()時第一次返回hello,第n次都返回world
        when(iterator.next()).thenReturn("hello").thenReturn("world");
        //使用mock的對象
        String result = iterator.next() + " " + iterator.next() + " " + iterator.next();
        //驗證結果
        assertEquals("hello world world",result);
    }
複製代碼
複製代碼
    @Test(expected = IOException.class)
    public void when_thenThrow() throws IOException {
        OutputStream outputStream = mock(OutputStream.class);
        OutputStreamWriter writer = new OutputStreamWriter(outputStream);
        //預設當流關閉時拋出異常
        doThrow(new IOException()).when(outputStream).close();
        outputStream.close();
    }
複製代碼

2.3 RETURNS_SMART_NULLS和RETURNS_DEEP_STUBS

RETURNS_SMART_NULLS實現了Answer接口的對象,它是建立mock對象時的一個可選參數,mock(Class,Answer)。

在建立mock對象時,有的方法咱們沒有進行stubbing,因此調用時會放回Null這樣在進行操做是極可能拋出NullPointerException。若是經過RETURNS_SMART_NULLS參數建立的mock對象在沒有調用stubbed方法時會返回SmartNull。例如:返回類型是String,會返回"";是int,會返回0;是List,會返回空的List。另外,在控制檯窗口中能夠看到SmartNull的友好提示。

複製代碼
    @Test
    public void returnsSmartNullsTest() {
        List mock = mock(List.class, RETURNS_SMART_NULLS);
        System.out.println(mock.get(0));
        
        //使用RETURNS_SMART_NULLS參數建立的mock對象,不會拋出NullPointerException異常。另外控制檯窗口會提示信息「SmartNull returned by unstubbed get() method on mock」
        System.out.println(mock.toArray().length);
    }
複製代碼

RETURNS_DEEP_STUBS也是建立mock對象時的備選參數

RETURNS_DEEP_STUBS參數程序會自動進行mock所需的對象,方法deepstubsTest和deepstubsTest2是等價的

複製代碼
    @Test
    public void deepstubsTest(){
        Account account=mock(Account.class,RETURNS_DEEP_STUBS);
        when(account.getRailwayTicket().getDestination()).thenReturn("Beijing");
        account.getRailwayTicket().getDestination();
        verify(account.getRailwayTicket()).getDestination();
        assertEquals("Beijing",account.getRailwayTicket().getDestination());
    }
    @Test
    public void deepstubsTest2(){
        Account account=mock(Account.class); 
        RailwayTicket railwayTicket=mock(RailwayTicket.class);        
        when(account.getRailwayTicket()).thenReturn(railwayTicket); 
        when(railwayTicket.getDestination()).thenReturn("Beijing");
        
        account.getRailwayTicket().getDestination();
        verify(account.getRailwayTicket()).getDestination();    
        assertEquals("Beijing",account.getRailwayTicket().getDestination());
    }    
    
    public class RailwayTicket{
        private String destination;

        public String getDestination() {
            return destination;
        }

        public void setDestination(String destination) {
            this.destination = destination;
        }        
    }
    
    public class Account{
        private RailwayTicket railwayTicket;

        public RailwayTicket getRailwayTicket() {
            return railwayTicket;
        }

        public void setRailwayTicket(RailwayTicket railwayTicket) {
            this.railwayTicket = railwayTicket;
        }
    }
複製代碼

2.4 模擬方法體拋出異常

    @Test(expected = RuntimeException.class)
    public void doThrow_when(){
        List list = mock(List.class);
        doThrow(new RuntimeException()).when(list).add(1);
        list.add(1);
    }

2.5 使用註解來快速模擬 

在上面的測試中咱們在每一個測試方法裏都mock了一個List對象,爲了不重複的mock,是測試類更具備可讀性,咱們可使用下面的註解方式來快速模擬對象:

    @Mock
    private List mockList;

OK,咱們再用註解的mock對象試試 

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

運行這個測試類你會發現報錯了,mock的對象爲NULL,爲此咱們必須在基類中添加初始化mock的代碼

複製代碼
public class MockitoExample2 {
    @Mock
    private List mockList;

    public MockitoExample2(){
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void shorthand(){
        mockList.add(1);
        verify(mockList).add(1);
    }
}
複製代碼

或者使用built-in runner:MockitoJUnitRunner

複製代碼
@RunWith(MockitoJUnitRunner.class)
public class MockitoExample2 {
    @Mock
    private List mockList;

    @Test
    public void shorthand(){
        mockList.add(1);
        verify(mockList).add(1);
    }
}
複製代碼

2.6 參數匹配

複製代碼
    @Test
    public void with_arguments(){
        Comparable comparable = mock(Comparable.class);
        //預設根據不一樣的參數返回不一樣的結果
        when(comparable.compareTo("Test")).thenReturn(1);
        when(comparable.compareTo("Omg")).thenReturn(2);
        assertEquals(1, comparable.compareTo("Test"));
        assertEquals(2, comparable.compareTo("Omg"));
        //對於沒有預設的狀況會返回默認值
        assertEquals(0, comparable.compareTo("Not stub"));
    }
複製代碼

除了匹配製定參數外,還能夠匹配本身想要的任意參數

複製代碼
    @Test
    public void with_unspecified_arguments(){
        List list = mock(List.class);
        //匹配任意參數
        when(list.get(anyInt())).thenReturn(1);
        when(list.contains(argThat(new IsValid()))).thenReturn(true);
        assertEquals(1, list.get(1));
        assertEquals(1, list.get(999));
        assertTrue(list.contains(1));
        assertTrue(!list.contains(3));
    }

    private class IsValid extends ArgumentMatcher<List>{
        @Override
        public boolean matches(Object o) {
            return o == 1 || o == 2;
        }
    }
複製代碼

注意:若是你使用了參數匹配,那麼全部的參數都必須經過matchers來匹配,以下代碼:

複製代碼
    @Test
    public void all_arguments_provided_by_matchers(){
        Comparator comparator = mock(Comparator.class);
        comparator.compare("nihao","hello");
        //若是你使用了參數匹配,那麼全部的參數都必須經過matchers來匹配
        verify(comparator).compare(anyString(),eq("hello"));
        //下面的爲無效的參數匹配使用
        //verify(comparator).compare(anyString(),"hello");
    }
複製代碼

2.7 自定義參數匹配

複製代碼
    @Test
    public void argumentMatchersTest(){
        //建立mock對象
        List<String> mock = mock(List.class);

        //argThat(Matches<T> matcher)方法用來應用自定義的規則,能夠傳入任何實現Matcher接口的實現類。
        when(mock.addAll(argThat(new IsListofTwoElements()))).thenReturn(true);

        mock.addAll(Arrays.asList("one","two","three"));
        //IsListofTwoElements用來匹配size爲2的List,由於例子傳入List爲三個元素,因此此時將失敗。
        verify(mock).addAll(argThat(new IsListofTwoElements()));
    }
    
    class IsListofTwoElements extends ArgumentMatcher<List>
    {
        public boolean matches(Object list)
        {
            return((List)list).size()==2;
        }
    }
複製代碼

2.8 捕獲參數來進一步斷言

較複雜的參數匹配器會下降代碼的可讀性,有些地方使用參數捕獲器更加合適。

複製代碼
        @Test
    public void capturing_args(){
        PersonDao personDao = mock(PersonDao.class);
        PersonService personService = new PersonService(personDao);

        ArgumentCaptor<Person> argument = ArgumentCaptor.forClass(Person.class);
        personService.update(1,"jack");
        verify(personDao).update(argument.capture());
        assertEquals(1,argument.getValue().getId());
        assertEquals("jack",argument.getValue().getName());
    }

     class Person{
        private int id;
        private String name;

        Person(int id, String name) {
            this.id = id;
            this.name = name;
        }

        public int getId() {
            return id;
        }

        public String getName() {
            return name;
        }
    }

    interface PersonDao{
        public void update(Person person);
    }

    class PersonService{
        private PersonDao personDao;

        PersonService(PersonDao personDao) {
            this.personDao = personDao;
        }

        public void update(int id,String name){
            personDao.update(new Person(id,name));
        }
    }
複製代碼

2.9 使用方法預期回調接口生成指望值(Answer結構)

複製代碼
@Test
    public void answerTest(){
        when(mockList.get(anyInt())).thenAnswer(new CustomAnswer());
        assertEquals("hello world:0",mockList.get(0));
        assertEquals("hello world:999",mockList.get(999));
    }

    private class CustomAnswer implements Answer<String>{
        @Override
        public String answer(InvocationOnMock invocation) throws Throwable {
            Object[] args = invocation.getArguments();
            return "hello world:"+args[0];
        }
    }
複製代碼

也可以使用匿名內部類實現

複製代碼
    @Test
    public void answer_with_callback(){
        //使用Answer來生成咱們咱們指望的返回
        when(mockList.get(anyInt())).thenAnswer(new Answer<Object>() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                Object[] args = invocation.getArguments();
                return "hello world:"+args[0];
            }
        });
        assertEquals("hello world:0",mockList.get(0));
        assertEquals("hello world:999",mockList.get(999));
    }
複製代碼

2.10 修改對未預設的調用返回默認指望

複製代碼
    @Test
    public void unstubbed_invocations(){
        //mock對象使用Answer來對未預設的調用返回默認指望值
        List mock = mock(List.class,new Answer() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                return 999;
            }
        });
        //下面的get(1)沒有預設,一般狀況下會返回NULL,可是使用了Answer改變了默認指望值
        assertEquals(999, mock.get(1));
        //下面的size()沒有預設,一般狀況下會返回0,可是使用了Answer改變了默認指望值
        assertEquals(999,mock.size());
    }
複製代碼

2.11 用spy監控真實對象  

  • Mock不是真實的對象,它只是用類型的class建立了一個虛擬對象,並能夠設置對象行爲
  • Spy是一個真實的對象,但它能夠設置對象行爲
  • InjectMocks建立這個類的對象並自動將標記@Mock、@Spy等註解的屬性值注入到這個中
複製代碼
    @Test(expected = IndexOutOfBoundsException.class)
    public void spy_on_real_objects(){
        List list = new LinkedList();
        List spy = spy(list);
        //下面預設的spy.get(0)會報錯,由於會調用真實對象的get(0),因此會拋出越界異常
        //when(spy.get(0)).thenReturn(3);

        //使用doReturn-when能夠避免when-thenReturn調用真實對象api
        doReturn(999).when(spy).get(999);
        //預設size()指望值
        when(spy.size()).thenReturn(100);
        //調用真實對象的api
        spy.add(1);
        spy.add(2);
        assertEquals(100,spy.size());
        assertEquals(1,spy.get(0));
        assertEquals(2,spy.get(1));
        verify(spy).add(1);
        verify(spy).add(2);
        assertEquals(999,spy.get(999));
        spy.get(2);
    }
複製代碼

2.12 真實的部分mock

複製代碼
    @Test
    public void real_partial_mock(){
        //經過spy來調用真實的api
        List list = spy(new ArrayList());
        assertEquals(0,list.size());
        A a  = mock(A.class);
        //經過thenCallRealMethod來調用真實的api
        when(a.doSomething(anyInt())).thenCallRealMethod();
        assertEquals(999,a.doSomething(999));
    }


    class A{
        public int doSomething(int i){
            return i;
        }
    }
複製代碼

2.13 重置mock

複製代碼
    @Test
    public void reset_mock(){
        List list = mock(List.class);
        when(list.size()).thenReturn(10);
        list.add(1);
        assertEquals(10,list.size());
        //重置mock,清除全部的互動和預設
        reset(list);
        assertEquals(0,list.size());
    }
複製代碼

2.14 驗證確切的調用次數

複製代碼
    @Test
    public void verifying_number_of_invocations(){
        List list = mock(List.class);
        list.add(1);
        list.add(2);
        list.add(2);
        list.add(3);
        list.add(3);
        list.add(3);
        //驗證是否被調用一次,等效於下面的times(1)
        verify(list).add(1);
        verify(list,times(1)).add(1);
        //驗證是否被調用2次
        verify(list,times(2)).add(2);
        //驗證是否被調用3次
        verify(list,times(3)).add(3);
        //驗證是否從未被調用過
        verify(list,never()).add(4);
        //驗證至少調用一次
        verify(list,atLeastOnce()).add(1);
        //驗證至少調用2次
        verify(list,atLeast(2)).add(2);
        //驗證至多調用3次
        verify(list,atMost(3)).add(3);
    }
複製代碼

2.15 連續調用

複製代碼
    @Test(expected = RuntimeException.class)
    public void consecutive_calls(){
        //模擬連續調用返回指望值,若是分開,則只有最後一個有效
        when(mockList.get(0)).thenReturn(0);
        when(mockList.get(0)).thenReturn(1);
        when(mockList.get(0)).thenReturn(2);
        when(mockList.get(1)).thenReturn(0).thenReturn(1).thenThrow(new RuntimeException());
        assertEquals(2,mockList.get(0));
        assertEquals(2,mockList.get(0));
        assertEquals(0,mockList.get(1));
        assertEquals(1,mockList.get(1));
        //第三次或更多調用都會拋出異常
        mockList.get(1);
    }
複製代碼

2.16 驗證執行順序

複製代碼
    @Test
    public void verification_in_order(){
        List list = mock(List.class);
        List list2 = mock(List.class);
        list.add(1);
        list2.add("hello");
        list.add(2);
        list2.add("world");
        //將須要排序的mock對象放入InOrder
        InOrder inOrder = inOrder(list,list2);
        //下面的代碼不能顛倒順序,驗證執行順序
        inOrder.verify(list).add(1);
        inOrder.verify(list2).add("hello");
        inOrder.verify(list).add(2);
        inOrder.verify(list2).add("world");
    }
複製代碼

2.17 確保模擬對象上無互動發生

複製代碼
    @Test
    public void verify_interaction(){
        List list = mock(List.class);
        List list2 = mock(List.class);
        List list3 = mock(List.class);
        list.add(1);
        verify(list).add(1);
        verify(list,never()).add(2);
        //驗證零互動行爲
        verifyZeroInteractions(list2,list3);
    }
複製代碼

2.18 找出冗餘的互動(即未被驗證到的)

複製代碼
    @Test(expected = NoInteractionsWanted.class)
    public void find_redundant_interaction(){
        List list = mock(List.class);
        list.add(1);
        list.add(2);
        verify(list,times(2)).add(anyInt());
        //檢查是否有未被驗證的互動行爲,由於add(1)和add(2)都會被上面的anyInt()驗證到,因此下面的代碼會經過
        verifyNoMoreInteractions(list);

        List list2 = mock(List.class);
        list2.add(1);
        list2.add(2);
        verify(list2).add(1);
        //檢查是否有未被驗證的互動行爲,由於add(2)沒有被驗證,因此下面的代碼會失敗拋出異常
        verifyNoMoreInteractions(list2);
    }
複製代碼

3 Mockito如何實現Mock[3]


 返回

Mockito並非建立一個真實的對象,而是模擬這個對象,他用簡單的when(mock.method(params)).thenRetrun(result)語句設置mock對象的行爲,以下語句:

// 設置mock對象的行爲 - 當調用其get方法獲取第0個元素時,返回"first"
Mockito.when(mockedList.get(0)).thenReturn("first");

在Mock對象的時候,建立一個proxy對象,保存被調用的方法名(get),以及調用時候傳遞的參數(0),而後在調用thenReturn方法時再把「first」保存起來,這樣,就有了構建一個stub方法所需的全部信息,構建一個stub。當get方法被調用的時候,實際上調用的是以前保存的proxy對象的get方法,返回以前保存的數據。

參考

[1] 單元測試之Stub和Mock

[2] mockito簡單教程

[3] Mockito入門

[4] 學習Mockito

相關文章
相關標籤/搜索