Mockito學習(1) --- 快速入門


Mockito是一個模擬測試框架,可以讓你用優雅,簡潔的接口寫出漂亮的單元測試。Mockito可以讓單元測試易於可讀,產生簡潔的校驗錯誤。

1、如何使用Mockito

引入mavne依賴

<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-core</artifactId>
  <version>2.23.4</version>
  <scope>test</scope>
</dependency>

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

有三種方法可以配置使用 Mockito

MockitoJUnitRunner 注解

接下來主要用這種方法來介紹

import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.Mock;

// 測試類加上 @RunWith(MockitoJUnitRunner.class) 注解
@RunWith(MockitoJUnitRunner.class)
public class MockByRunnerTest {
    @Mock
    private AccountDao accountDao;
}

MockitoAnnotations 方式

import org.mockito.MockitoAnnotations;
import org.mockito.Mock;
import org.junit.Before;

public class MockByAnnotationTest {
    @Mock
    private AccountDao accountDao;
    
    @Before
    public void init(){
      	// 初始化
        MockitoAnnotations.initMocks(this);
    }
}

@Rule 注解

import org.junit.Rule;
import org.mockito.Mock;

public class MockByRuleTest {
  	// 初始化
    @Rule
    public MockitoRule mockitoRule = MockitoJUnit.rule();
    
    @Mock
    AccountDao accountDao;
}

2、什么是Mock測試

mock測試就是在測試過程中,對於某些不容易構造或者不容易獲取的對象,用一個虛擬的對象來創建以便測試的測試方法。

比如一個類A,有B、C、D等多個復雜類的成員變量。如果我們測試時候通過new A()的方式來創建A對象,就需要同時手動創建B、C、D三個對象,並和A關聯,提高了測試的復雜性。Mock可以自動生成一個虛擬的A對象,就是幫我們省去了這些復雜的創建流程。

Mockito提供了兩種Mock的方式:

import org.mockito.Mock;
import static org.mockito.Mockito.mock;

@RunWith(MockitoJUnitRunner.class)
public class MockByRunnerTest2 {
    // 使用@Mock注解
    @Mock
    private AccountDao accountDao;
    
    @Test
    public void test1() {
        Account account = accountDao.findById(1);
      	// 返回null
        System.out.println(account;
        // 輸出 class com.sanyue.learn.mockito.mockitodemo.domain.Account$MockitoMock$1675715420
        System.out.println(account.getClass());          
    }
    
    public void test2() {
        // 使用mock方法
        AccountDao accountDao = mock(AccountDao.class, Mockito.RETURNS_SMART_NULLS);
        Account account = accountDao.findById(1);
        // 返回null
      	System.out.println(accoun;
    }
}

Mock生成的是一個代理對象,默認情況下,執行對象的所有的方法都返回該方法的返回類型的默認值,不會真正去執行該對象的方法。既然這樣,那我們在測試中如何使用這個mock出來的對象,來執行方法進行測試呢?這就需要使用到Mockito的Stub(測試樁)來設置Mock對象方法的返回值了。

3、Stub(測試樁) 介紹

上面介紹了Mock生成的對象,其實是一個代理對象,不會真正去執行類里面的方法。為了便於我們測試,我們需要用到Stub來設置我們期望的方法返回值,可以理解為創建測試用例

// 你可以mock具體的類型,不僅只是接口
LinkedList mockedList = mock(LinkedList.class);

// 開始設置測試樁
// 當get(0)被調用時,返回"first"
when(mockedList.get(0)).thenReturn("first");

// 方法get(1)被調用時,拋異常。
when(mockedList.get(1)).thenThrow(new RuntimeException());

// 輸出 "first"
System.out.println(mockedList.get(0));

// 拋出異常
System.out.println(mockedList.get(1));

// 輸出 null,因為get(999)的調用沒有被設置過
System.out.println(mockedList.get(999));

由上面例子可以看到,Stub就是人為指定 當使用該參數調用該方法時,方法返回什么值。下面介紹一些其他的Stud方式:

 // 使用doReturn語句和when語句一樣的效果
 doReturn(1).when(mockedList).get(1);
 // 輸出 1
 System.out.println(mockedList.get(1));

 // 使用doNothing來設置void返回值的方法
 doNothing().when(mockedList).clear();
 // 設置執行clear方法拋出異常
 doThrow(new RuntimeException()).when(mockedList).clear();
 mockedList.clear();
 // 以下斷言表示,mockedList的clear方法被調用了1次
 verify(mockedList, times(1)).clear();

設置每次調用返回不同的值

如果希望每次調用的返回值都不一樣可以這樣設置:

// 第1次調用返回2,第2次返回2,以后再調用返回3
when(mockedList.size()).thenReturn(1, 2, 3);
// 等價寫法
// when(mockedList.size()).thenReturn(1).thenReturn(2).thenReturn(3).thenReturn(4);
// 1
System.out.println(mockedList.size());
// 2
System.out.println(mockedList.size());
// 3
System.out.println(mockedList.size());
// 超過3次后調用,也返回3
System.out.println(mockedList.size());

也可以通過thenAnswer方式來設置不同調用次數返回不同的值:

// 設置返回值是 參數值*10
when(list.get(anyInt())).thenAnswer(new Answer(){
    @Override
    public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
      int arguments = invocationOnMock.getArgument(0);
      return 10*arguments;
    }
});

參數匹配器

設置用參數匹配器根據不同類型參數,返回不同的值:

 public class TestService {
   // 定義一個方法
   public String say(String param1, Integer param2, String param3) {
     return "hello";
   }
 }

@Test
public void test3(){
  TestService testService = mock(TestService.class);

  // anyString() 表示任何字符串參數,anyInt() 表示任何int類型參數
  when(testService.say(anyString(), anyInt(), anyString())).thenReturn("world");
  // 輸出 world
  System.out.println(testService.say("x", 1, "x"));
  // 如果參數列表包含參數匹配器,則必能出現具體參數值,要使用eq() 方法代替
  // when(testService.say(anyString(), 1, anyString())).thenReturn("world2");
  when(testService.say(anyString(), eq(1), anyString())).thenReturn("world2");
  // 輸出 world2
  System.out.println(testService.say("x", 1, "x"));
}

設置執行真實的方法

可以使用thenCallRealMethod來設置執行對象真正的方法

 List list = mock(LinkedList.class);
 when(list.size()).thenCallRealMethod();

重置Mock對象

使用reset方法可以重置Mock對象Stub的設置

List mock = mock(List.class);
when(mock.size()).thenReturn(10);
mock.add(1);
reset(mock);

do系列方法的運用

當你調用doThrow(), doAnswer(), doNothing(), doReturn() and doCallRealMethod() 這些函數時可以在適當的位置調用when()函數. 當你需要下面這些功能時這是必須的:

  • 測試void函數
  • 在受監控的對象上測試函數
  • 不知一次的測試為同一個函數,在測試過程中改變mock對象的行為,比如為Spy對象進行Stub

anyObject(), eq()這樣的匹配器函數不會返回匹配器。它們會在內部將匹配器記錄到一個棧當中,並且返回一個假的值,通常為null。這樣的實現是由於被Java編譯器強加的靜態類型安全。結果就是你不能在驗證或者測試樁函數之外使用anyObject(), eq()函數。

如果一個方法沒有被Stub設置,會返回該方法返回類型的默認值,比如int類型返回0,boolean返回false,對象類型返回null。

需要記住的是 mock對象會覆蓋整個被mock的對象,因此沒有stub的方法只能返回默認值,並且類的方法不會真正的執行。

4、Spy 介紹

Mock出來的對象(代理對象),默認不會去真正執行類的方法。而用Spy聲明的對象(真實對象),則會默認執行真正的方法。

/** 
* 也可以使用@Spy注解方式初始化spy對象
* @Spy
* List<Integer> list = new ArrayList<>();
**/
List<Integer> realList = new ArrayList<>();
List<Integer> list = spy(realList);
list.add(1);
list.add(2);

// 分別輸出1和2,說明真正執行了add和get方法
System.out.println(list.get(0));
System.out.println(list.get(1));

// 進行部分mock
when(list.isEmpty()).thenReturn(true);
// 輸出true,說明isEmpty方法被mock了
System.out.println(list.isEmpty());
// 分別輸出1和2,說明get方法不受mock影響
System.out.println(list.get(0));
System.out.println(list.get(1));

需要注意的是,如果為Spy出來的對象進行Stub,有時候不能使用when,因為Spy對象調用方法時,會調用真實的方法。比如以下例子:

List list = new LinkedList();
List spy = spy(list);

// 不可能 : 因為當調用spy.get(0)時會調用真實對象的get(0)函數,此時會發生IndexOutOfBoundsException異常,因為真實List對象是空的
when(spy.get(0)).thenReturn("foo");
System.out.println(spy.get(0));

// 你需要使用doReturn()來打樁
doReturn("foo").when(spy).get(0);
System.out.println(spy.get(0));

SpyMock的相同點和區別:

  1. 得到的對象同樣可以進行“監管”,即驗證和打樁。

  2. 如果不對spy對象的methodA打樁,那么調用spy對象的methodA時,會調用真實方法。

  3. 如果不對mock對象的methodA打樁,將doNothing,且返回默認值(null,0,false)。

5、斷言

Mockito中斷言的使用和Junit的一樣,這里舉幾個例子,不詳細描述:

List<Integer> list = mock(List.class);
        
// 斷言list.get(0)值等於1
assertThat(list.get(0), equalTo(1));

// 斷言大於50
assertThat(list.get(0), greaterThan(20));
// 斷言小於等於50
assertThat(list.get(0), lessThanOrEqualTo(50));

// 斷言 必須大於20 並且 小於等於50(所有條件成立)
assertThat(list.get(0), allOf(greaterThan(20), lessThanOrEqualTo(50)));
// 斷言 必須大於20 或 小於等於50(其中至少一個條件成立)
assertThat(list.get(0), oneOf(greaterThan(20), lessThanOrEqualTo(50)));

// 斷言任何條件都成立
assertThat(list.get(0), anything());
// 斷言等於1
assertThat(list.get(0), is(1));
// 斷言不等於-1
assertThat(list.get(0), not(-1));
// 斷言返回的字符串包含1
assertThat(list.get(0), containsString("1"));
// 斷言返回的字符串以1開頭
assertThat(list.get(0), startsWith("1"));
// 斷言該異常屬於RuntimeException
assertThat(e, instanceOf(RuntimeException.class));

可以這樣斷言異常

try {
	list.clear();
	// 如果執行到這一步,返回失敗
	fail();
} catch (Exception e) {
	assertThat(e, instanceOf(RuntimeException.class));
}

6、驗證函數的調用次數

Mockito可以對函數的執行過程進行斷言,通過斷言函數的執行次數,要對方法執行邏輯進行判斷。

List<Integer> mockedList = mock(List.class);
 mockedList.add("once");
 mockedList.add("twice");
 mockedList.add("twice");
 mockedList.add("three times");
 mockedList.add("three times");
 mockedList.add("three times");

 // 下面的兩個驗證函數效果一樣,期望mockedList的add("once")方法執行了1次
 verify(mockedList).add("once");
 verify(mockedList, times(1)).add("once");

 // 驗證具體的執行次數,分別希望是2次和3次
 verify(mockedList, times(2)).add("twice");
 verify(mockedList, times(3)).add("three times");

 // 使用never()進行驗證,never相當於times(0),即沒有執行過
 verify(mockedList, never()).add("never happened");

 // 使用atLeast()至少執行次數/atMost()最多執行次數
 verify(mockedList, atLeastOnce()).add("three times");
 verify(mockedList, atLeast(2)).add("five times");
 verify(mockedList, atMost(5)).add("three times");

7、驗證方法的執行順序

可以使用InOrder來對方法的執行順序進行驗證

 // 進行mock
List singleMock = mock(List.class);
singleMock.add("was added first");
singleMock.add("was added second");

// 為該mock對象創建一個inOrder對象
InOrder inOrder = inOrder(singleMock);

// 確保add函數首先執行的是add("was added first"),然后才是add("was added second")
inOrder.verify(singleMock).add("was added first");
inOrder.verify(singleMock).add("was added second");


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM