【Java技術(shù)深入解析】「核心技術(shù)提升」最流行的Java模擬框架Mockito入門指南(Java單元測試)

官方資源

官方網(wǎng)站

http://mockito.org

版本介紹

還在使用 Mockito 1.x?看看 Mockito 2 有哪些新功能侥猬!Mockito 3 沒有引入任何破壞性的 API 變動(dòng),但現(xiàn)在需要 Java 8 而不是 Mockito 2 的 Java 6垒酬。 Mockito 4 刪除了過時(shí)的 API黎泣。Mockito 5 將默認(rèn) mockmaker 改為 mockito-inline绊含,現(xiàn)在需要 Java 11。一次只支持一個(gè)主要版本,而且不會(huì)向舊版本回傳更改內(nèi)容册着。

項(xiàng)目源碼

https://github.com/mockito/mockito

[圖片上傳失敗...(image-1c7a85-1704596577351)]

開發(fā)指南

添加maven依賴

這將在Maven項(xiàng)目中添加Mockito核心庫的依賴關(guān)系拴孤,并限定其范圍為測試(<scope>test</scope>)。這樣甲捏,您就可以在單元測試中使用Mockito框架來模擬對象和驗(yàn)證行為了演熟。請注意,您需要根據(jù)您的實(shí)際需求調(diào)整版本號司顿。

   <!-- 添加 Mockito 依賴 -->
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>3.12.4</version>
        <scope>test</scope>
    </dependency> 

獲得 Mockito 的推薦方法是使用自己喜歡的構(gòu)建系統(tǒng)聲明對 "mockito-core "庫的依賴绽媒。使用 Gradle 可以做到這一點(diǎn):

添加Gradle依賴

repositories { mavenCentral() }
dependencies { testImplementation "org.mockito:mockito-core:3.+" }}

Maven 用戶可以聲明對 mockito-core 的依賴。Mockito 會(huì)將每次更改作為 -SNAPSHOT 版本發(fā)布到公共 Sonatype 資源庫免猾。進(jìn)行手動(dòng)依賴關(guān)系管理的用戶可直接從 Maven Central 下載 jar是辕。使用手動(dòng)依賴關(guān)系管理的傳統(tǒng)版本可使用 1.* "mockito-all" 發(fā)行版。該發(fā)行版在 Mockito 2.* 中已停用猎提。

mockito需要junit配合使用

需要將JUnit和Mockito結(jié)合在一起使用來進(jìn)行Java單元測試获三。在添加Mockito依賴之前,請確保您的項(xiàng)目中已經(jīng)包含了JUnit的依賴锨苏。

<dependencies>
    <!-- 添加 JUnit 依賴 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>

    <!-- 添加 Mockito 依賴 -->
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>3.12.4</version>
        <scope>test</scope>
    </dependency>
</dependencies>

在上面的配置中疙教,我們添加了JUnit和Mockito的依賴關(guān)系,并將其范圍都設(shè)置為測試(<scope>test</scope>)伞租。這樣贞谓,就可以使用JUnit來運(yùn)行測試,并使用Mockito進(jìn)行對象模擬和行為驗(yàn)證葵诈。請確保根據(jù)您的實(shí)際需求調(diào)整JUnit和Mockito的版本號裸弦。

導(dǎo)入靜態(tài)資源

為了使測試類中的代碼更簡潔和易讀,您可以通過靜態(tài)導(dǎo)入來引入Mockito和JUnit的一些靜態(tài)資源作喘。這樣可以減少在測試類中使用這些資源時(shí)的冗余代碼

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

mockito的方法

驗(yàn)證互動(dòng)

下面是對應(yīng)相關(guān)的案例代碼:

@Test  
public void verify_behaviour(){  
    //模擬創(chuàng)建一個(gè)List對象
    List mock = mock(List.class);  
   // 或者使用 Mockito 4.10.0+ 時(shí)更簡單
    List mock = mock()理疙;
   // 使用 mock 對象 - 它不會(huì)拋出任何 "意外交互 "異常
    mock.add(1);  
    mock.clear();  
    //驗(yàn)證add(1)和clear()行為是否發(fā)生
    // 選擇性的、明確的泞坦、可讀性高的驗(yàn)證
    verify(mock).add(1);
    verify(mock).clear();
} 

詳細(xì)分析:使用mock()模擬對象

舉一個(gè)簡單的案例去模型mock數(shù)據(jù)效果窖贤,mock可以模擬各種各樣的對象,替代真正的對象做出希望的響應(yīng)贰锁。

//模擬LinkList的一個(gè)對象
LinkedList mockdedList = mock(LinkedList.class);
//此時(shí)條用get方法赃梧,會(huì)返回null,因?yàn)檫€沒有對方法調(diào)用的返回值做模擬豌熄。
System.out.printlin(mockedList.get(99));
模擬方法調(diào)用的返回值
mock對象被調(diào)用時(shí)的返回值

當(dāng)我們需要模擬獲取第一個(gè)元素并返回字符串“first”時(shí)授嘀,可以使用Mockito進(jìn)行樁模擬(stub)。樁模擬是指為特定的方法調(diào)用配置返回固定值的行為房轿。在官方文檔中粤攒,這種行為被稱為stub(存根)。通過使用樁模擬囱持,我們可以模擬本地對象以屏蔽對遠(yuǎn)程主機(jī)上對象的調(diào)用夯接。

when(mockedList.get(0).thenReturn("first"));
// 此時(shí)打印輸出first
System.out.println(mockedList.get(0));
詳細(xì)分析:模擬,方法調(diào)用拋出異常
// 模擬獲取第二個(gè)元素時(shí)纷妆,拋出RuntimeException
when(mockedList.get(1)).thenThrow(new RuntimeException);
// 此時(shí)拋出RuntimeException異常
System.out.println(mockedList.get(1));
// 沒有返回值類型的方法也可以模擬異常拋出:
doThrow(new RuntimeException()).when(mockedList).clear();
詳細(xì)分析:模擬調(diào)用方法時(shí)的參數(shù)匹配

anyInt()是Mockito中的一個(gè)匹配器盔几,它用于匹配任何傳入的int參數(shù)。這意味著無論傳入的參數(shù)是什么值掩幢,都將返回"element"逊拍。

when(mockedList.get(anyInt())).thenReturn("element");
// 此時(shí)打印是element
System.out.println(mockedList.get(99));

可以這樣描述anyInt()的作用:它用于接受任意的int參數(shù),并將其值忽略际邻,返回固定的字符串"element"芯丧。

詳細(xì)分析:模擬方法調(diào)用次數(shù)
// 調(diào)用add一次
mockedList.add("once");
// 驗(yàn)證add方法是否被調(diào)用了一次,兩種寫法效果一樣
verify(mockedList)add("once");
verify(mockedList,times(1)).add("once");

可以使用Mockito中的atLeast(int i)atMost(int i)來驗(yàn)證方法被調(diào)用的最小和最大次數(shù)限制世曾。

  • atLeast(int i)用于驗(yàn)證方法至少被調(diào)用了i
  • atMost(int i)用于驗(yàn)證方法最多被調(diào)用了i次缨恒。

可以精確地控制方法被調(diào)用的次數(shù),并進(jìn)行相應(yīng)的驗(yàn)證轮听。通過使用這些方法骗露,可以更準(zhǔn)確地?cái)嘌苑椒ǖ恼{(diào)用次數(shù)是否符合預(yù)期。

詳細(xì)分析:驗(yàn)證被測試類是否正確工作血巍,使用verify()

在默認(rèn)情況下萧锉,對于所有未被樁模擬過的有返回值的方法,Mockito會(huì)返回相應(yīng)的默認(rèn)值述寡。對于基本數(shù)據(jù)類型柿隙,如int,默認(rèn)值是0鲫凶;對于布爾類型优俘,默認(rèn)值是false;對于其他對象類型掀序,默認(rèn)值是null帆焕。

mock對象會(huì)覆蓋整個(gè)被mock的對象,因此沒有stub的方法只能返回默認(rèn)值不恭。重復(fù)stub兩次叶雹,則以第二次為準(zhǔn),如下將返回”second“换吧。

when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(0)).thenReturn("second");

下面這種形式表示第一次調(diào)用返回”first“折晦,第二次調(diào)用返回”second“,可以寫n多個(gè)

when(mockedList.get(0)).thenReturn("first").thenReturn("second");

如果實(shí)際調(diào)用次數(shù)超過了stub過的次數(shù)沾瓦,則會(huì)一直返回最后一次stub的值满着,如上例谦炒,第三次調(diào)用get(0),則返回 ”second“驗(yàn)證方法被調(diào)用特定的次數(shù)。

驗(yàn)證add方法被調(diào)用了兩次
verify(mockedList,times(2)).add("2");
驗(yàn)證add方法致至少被調(diào)用一次
verify(mockedList.atLeastOnce()).add("2");
驗(yàn)證add方法至少被調(diào)用兩次
verify(mockedList,atLeast(2)).add("2");
驗(yàn)證add方法最大被調(diào)用5次
verify(mockedList,atMost(5)).add("2");
驗(yàn)證add方法從未被調(diào)用

找到冗余的調(diào)用风喇,使用never();

verify(mockedList,never()).add("2");

模擬所期望的結(jié)果

為了模擬獲取和檢索數(shù)據(jù)的行為宁改,我們可以使用Mockito來進(jìn)行對象模擬。通過模擬數(shù)據(jù)獲取和檢索的過程魂莫,我們可以更輕松地編寫和執(zhí)行單元測試雀摘。

// 你可以模擬具體的類翘鸭,而不僅僅是接口
LinkedList mockedList = mock(LinkedList.class);
// 或者使用 Mockito 4.10.0+ 更簡單
// LinkedList mockedList = mock();
// 在實(shí)際執(zhí)行前出現(xiàn)存根
when(mockedList.get(0)).thenReturn("first")榛搔;
// 下面將打印 "first
System.out.println(mockedList.get(0))轧简;
// 下面打印 "null"括授,因?yàn)?get(999) 沒有被存根化
System.out.println(mockedList.get(999))脆炎;

模擬所期望的結(jié)果

@Test  
public void when_thenReturn(){  
    //mock一個(gè)Iterator類  
    Iterator iterator = mock(Iterator.class);  
    //預(yù)設(shè)當(dāng)iterator調(diào)用next()時(shí)第一次返回hello,第n次都返回world  
    when(iterator.next()).thenReturn("hello").thenReturn("world");
     //使用mock的對象  
    String result = iterator.next() + " " + iterator.next() + " " + iterator.next();  
    //驗(yàn)證結(jié)果  
    assertEquals("hello world world",result);  
 }

模擬方法體拋出異常

案例一

@Test(expected = IOException.class)注解中指定了期望的異常類型為IOException鞋邑,用于斷言在測試代碼中是否拋出了該異常最易,通過指定expected注解和模擬對象的預(yù)設(shè)行為,我們可以斷言在調(diào)用close()方法時(shí)是否會(huì)拋出IOException異常炫狱。

@Test(expected = IOException.class)  
public void when_thenThrow() throws IOException {  
    OutputStream outputStream = mock(OutputStream.class);  
    OutputStreamWriter writer = new OutputStreamWriter(outputStream);  
    //預(yù)設(shè)當(dāng)流關(guān)閉時(shí)拋出異常  
    doThrow(new IOException()).when(outputStream).close();  
    outputStream.close();  
}  

使用doThrow()方法配置了當(dāng)outputStreamclose()方法被調(diào)用時(shí)拋出IOException異常的預(yù)設(shè)行為藻懒。,調(diào)用outputStreamclose()方法视译,觸發(fā)異常拋出嬉荆。

案例二

@Test(expected = RuntimeException.class)注解中指定了期望的異常類型為RuntimeException,用于斷言在測試代碼中是否拋出了該異常酷含。

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

使用doThrow()方法配置了當(dāng)listadd(1)方法被調(diào)用時(shí)拋出RuntimeException異常的預(yù)設(shè)行為鄙早,調(diào)用listadd(1)方法,觸發(fā)異常拋出椅亚。通過指定expected注解和模擬對象的預(yù)設(shè)行為限番,我們可以斷言在調(diào)用add(1)方法時(shí)是否會(huì)拋出RuntimeException異常。

驗(yàn)證執(zhí)行順序

下面代碼用于驗(yàn)證在特定順序下方法的執(zhí)行狀況呀舔,使用inOrder.verify()方法按順序驗(yàn)證了方法的調(diào)用弥虐。

@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);
    //下面的代碼不能顛倒順序,驗(yàn)證執(zhí)行順序  
    inOrder.verify(list).add(1);  
    inOrder.verify(list2).add("hello");  
    inOrder.verify(list).add(2);  
    inOrder.verify(list2).add("world");  
}  

驗(yàn)證順序

先驗(yàn)證list的add(1)方法被調(diào)用媚赖,然后驗(yàn)證list2的add("hello")方法被調(diào)用霜瘪,接著驗(yàn)證list的add(2)方法被調(diào)用,最后驗(yàn)證list2的add("world")方法被調(diào)用惧磺。

通過使用InOrder對象颖对,我們可以驗(yàn)證方法的執(zhí)行順序是否符合預(yù)期。如果按照預(yù)期順序執(zhí)行磨隘,則測試將成功通過缤底。如果順序不符合預(yù)期顾患,測試將失敗。

未完待續(xù)

敬請期待:【Java技術(shù)深入解析】「核心技術(shù)提升」最流行的Java模擬框架Mockito入門指南(進(jìn)階學(xué)習(xí)案例)个唧。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末江解,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子坑鱼,更是在濱河造成了極大的恐慌膘流,老刑警劉巖絮缅,帶你破解...
    沈念sama閱讀 212,332評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鲁沥,死亡現(xiàn)場離奇詭異,居然都是意外死亡耕魄,警方通過查閱死者的電腦和手機(jī)画恰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,508評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吸奴,“玉大人允扇,你說我怎么就攤上這事≡虬拢” “怎么了考润?”我有些...
    開封第一講書人閱讀 157,812評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長读处。 經(jīng)常有香客問我糊治,道長,這世上最難降的妖魔是什么罚舱? 我笑而不...
    開封第一講書人閱讀 56,607評論 1 284
  • 正文 為了忘掉前任井辜,我火速辦了婚禮,結(jié)果婚禮上管闷,老公的妹妹穿的比我還像新娘粥脚。我一直安慰自己,他們只是感情好包个,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,728評論 6 386
  • 文/花漫 我一把揭開白布刷允。 她就那樣靜靜地躺著,像睡著了一般碧囊。 火紅的嫁衣襯著肌膚如雪恃锉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,919評論 1 290
  • 那天呕臂,我揣著相機(jī)與錄音破托,去河邊找鬼。 笑死歧蒋,一個(gè)胖子當(dāng)著我的面吹牛土砂,可吹牛的內(nèi)容都是我干的州既。 我是一名探鬼主播,決...
    沈念sama閱讀 39,071評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼萝映,長吁一口氣:“原來是場噩夢啊……” “哼吴叶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起序臂,我...
    開封第一講書人閱讀 37,802評論 0 268
  • 序言:老撾萬榮一對情侶失蹤蚌卤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后奥秆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逊彭,經(jīng)...
    沈念sama閱讀 44,256評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,576評論 2 327
  • 正文 我和宋清朗相戀三年构订,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了侮叮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,712評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡悼瘾,死狀恐怖囊榜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情亥宿,我是刑警寧澤卸勺,帶...
    沈念sama閱讀 34,389評論 4 332
  • 正文 年R本政府宣布,位于F島的核電站烫扼,受9級特大地震影響曙求,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜材蛛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,032評論 3 316
  • 文/蒙蒙 一圆到、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧卑吭,春花似錦芽淡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至掷邦,卻和暖如春白胀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背抚岗。 一陣腳步聲響...
    開封第一講書人閱讀 32,026評論 1 266
  • 我被黑心中介騙來泰國打工或杠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宣蔚。 一個(gè)月前我還...
    沈念sama閱讀 46,473評論 2 360
  • 正文 我出身青樓向抢,卻偏偏與公主長得像认境,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子挟鸠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,606評論 2 350

推薦閱讀更多精彩內(nèi)容