mockito是一款非常不錯的模擬測試框架,他可以按照你的需求,來生成一個模擬框架,這樣給我的工作中解決了很多問題,比如做單元測試,service依賴dao,如果我測試service層方法,但是調用dao的方法,第一他會打印很多讓我討厭的信息,但是我也不想去頻繁修改log4j,第二 這樣速度確實有些慢。
那麼,我可以模擬一個dao對象出來,並且預先設置好了,你這個dao對象調用方法,返回什麼什麼值,這樣,當我創建一個dao模擬對象,並不會真的創建dao對象,但是我通過預先設置了某些方法的返回值,讓我測試類來使用一下,這樣,達到了我需要的效果。
我把我學習、練習的代碼貼出如下:
package testmock;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
public class Test01 {
// mock模擬對象,只是模擬對象的臉,並不會真的生成該對象,所以你會發現,模擬對象在使用時,調用該對象的方法,就和正常new出來對象使用會有這麼一種感覺,那就是首位不呼應,比如給一個字段set設置了,然後緊接着get獲取,你會發現並沒有獲取到,這就是因爲壓根就沒有對象,只不過是模擬了一下該方法而已!那怎麼生成get方法可以獲取返回值,在下面的test方法中說
@Test
public void test01() {
// 創建迭代器的模擬對象
Iterator i = mock(Iterator.class);
// 當第一次調用next方法返回hello,第二次調用返回world
when(i.next()).thenReturn("Hello").thenReturn("World");
// 調用next方法,拼接字符串
String result = i.next() + " " + i.next();
// 驗證i的times方法調用了2次
verify(i, times(2)).next();
}
// 通過接口生成模擬對象,可以預先設置當用該對象的某個方法,返回的值,用作之後的判斷
@Test
public void test02() {
// 創建list模擬對象
List mock = mock(List.class);
// 用該when().thenReturn()方式,設置了,當執行某個方法的時候,第一次、第二次……第n次返回的值
when(mock.get(1)).thenReturn("abcd").thenReturn("eeee").thenReturn("dddd");
// 執行方法
Object object = mock.get(1);
System.out.println(object);
// 驗證是否執行了該方法
verify(mock).get(1);
// 當執行mock.get(3)來返回一個並不存在的元素時,就會返回該元素類型的默認值
// 如下返回的是null,因爲引用類型默認值 null
Object object2 = mock.get(3);
System.out.println("mock.get(3)獲取一個並沒有預先定義傳入參數3,返回結果,所以結果爲:" + object2);
}
// 當然,除了可以模擬接口,也可以模擬類
@Test
public void test03() {
// 創建arraylist模擬對象
ArrayList<String> mock = mock(ArrayList.class);
// 當使用set存、get取的時候,就會發現,並無法取出存的數據,因爲這個mock只是一個模擬對象,並不是真正的集合對象
mock.set(1, "aa");
// 打印結果爲null值
System.out.println(mock.get(1));
// 所以使用mockito提供的方式來對調用某個方法產生的返回值進行手動設置
// 指定第一次調用get方法返回num1,第二次返回num2,這種方式官方翻譯叫存根:stubbing,個人理解爲預定義返回值的意思。
when(mock.get(0)).thenReturn("num1").thenReturn("num2");
// 打印獲取結果,會發現如預想的一樣
System.out.println(mock.get(0));
// 也可以通過驗證方法來驗證是否執行了該方法,如果確實調用了get(0)方法,則測試通過,否則測試失敗
verify(mock).get(0);
// 如果調用了2次mock.get(0)方法,再使用上面的方式測試會發現測試失敗
mock.get(0);
System.out.println("調用了2次get(0)方法");
// 下面這句代碼是錯誤的
// verify(mock).get(0);
// 那麼,當調用了2次調用get(0)方法,應該使用如下格式進行測試:
verify(mock, times(2)).get(0);
}
/*
* 參數匹配器,當給mock對象傳參,我們傳入的參數都是固定值,mockito也提供了匹配任意參數值的方法代替具體參數值來傳參。 /
* 如:任意一個數字可以代表數字123,任意一個字符串可以代表字符串 "abcdString" /
* 那麼,mockito提供的方式爲:anyInt:代表任意一個int值,anyString:代表任意一個字符串,以此類推,
* anyXxx代表對應的類型數據,如:8種基本數據類型,多個集合類型,Object、Class等等都有對應的參數匹配器,
* 可以從Matchers類中翻閱察看!
*/
@Test
public void test04() {
// 模擬map類型對象
HashMap<String, String> mock = mock(HashMap.class);
// 預先設定返回值
when(mock.get(anyString())).thenReturn("第一個值").thenReturn("第二個值");
// 察看anyString是否能匹配到調用mock.get()時候,傳入的任意參數
// 第一次調用,傳入參數value1,anyString()能匹配
String string = mock.get("value1");
// 打印結果:第一個值
System.out.println(string);
// 第二個調用,傳入參數value2,anyString()能匹配
string = mock.get("value2");
// 打印結果:第二個值
System.out.println(string);
}
// 精確匹配,使用上面的類型匹配器,確實很好用,但是如果說,我們需要精確匹配某一個值的話,那麼,就使用mockito提供的方法eq(),同樣,該方法的參數也可以傳遞N多種類型
@Test
public void test05() {
// 創建hashMap的模擬對象
HashMap mock = mock(HashMap.class);
// 指定匹配value字符串,即設置map的鍵爲value對應到的值爲:指定的value鍵的值
// 多設置幾個,看區別
// 這裏要注意,anyXxx()方法設置返回值,一定要寫到eq()方法設置返回值前面,否則anyString會覆蓋之前設置的eq,那麼再執行的話,返回的值就都是:anyString()設置的值了。
when(mock.get(anyString())).thenReturn("其他的值");
when(mock.get(eq("value1"))).thenReturn("指定value鍵的值");
when(mock.get(eq("value2"))).thenReturn("value2鍵的值");
// 察看結果
// 結果爲
System.out.println(mock.get("aaa"));
System.out.println(mock.get("value1"));
System.out.println(mock.get("value2"));
}
// 如果在設置存根的時候,指定參數爲anyString(),那麼使用verify()方法進行驗證方法是否被調用,也可以使用anyString()來傳參
@Test
public void test06() {
HashMap mock = mock(HashMap.class);
when(mock.get(anyString())).thenReturn("其他的值");
System.out.println(mock.get("aaa"));
// 當驗證方法的時候,也可以使用anyString()與定義返回值時一致的方法來驗證該方法是否被調用。
verify(mock).get(anyString());
}
// 次數匹配:驗證調用了幾次傳入同一個參數的方法,如下案例,調用add方法,a傳入1次,bb傳入2次,cc傳入3次
@Test
public void test07() {
// 創建模擬對象
ArrayList<String> mock = mock(ArrayList.class);
mock.add("aa");
mock.add("bb");
mock.add("bb");
mock.add("cc");
mock.add("cc");
mock.add("cc");
// 驗證,verity方法傳參爲:mock對象,time(次數)
// 傳入aa的add方法調用了1次
verify(mock, times(1)).add("aa");
// 傳入bb的add方法調用了2次
verify(mock, times(2)).add("bb");
// 傳入cc的add方法調用了3次
verify(mock, times(3)).add("cc");
// 如果不傳參 time()方法,默認是time(1)
// verify(mock ).add("cc");//出現了3次,所以測試失敗
verify(mock).add("aa");// 出現了1次,測試通過
// 如果調用add從來沒有傳遞過某個參數,可以將never()傳入,進行測試,如下:
// verify(mock, never()).add("aa"); //錯誤,因爲傳參aa是出現過的
verify(mock, never()).add("從來沒有給add方法傳入這個字符串");
// 也可以使用其他的
// 最少出現一次
verify(mock, atLeastOnce()).add("bb");
// 出現的次數小於或等於指定的次數,如下bb出現的次數小於或等於2,傳3就測試失敗
verify(mock, atLeast(2)).add("bb");
// 出現的次數大於或等於指定的次數,如下cc出現的次數大於或等於5次
verify(mock, atMost(3)).add("cc");
}
// 通過使用Answer接口的匿名內部類方式設置存根
@Test
public void test08() {
HashMap mock = mock(HashMap.class);
when(mock.get(anyString())).thenAnswer(new Answer<String>() {
public String answer(InvocationOnMock invocation) throws Throwable {
// 通過invocation可以獲取調用該方法時傳遞的參數
Object[] arguments = invocation.getArguments();
// 我們打印察看一下
for (Object object : arguments) {
System.out.println(object);
}
// 返回字符串,這樣當調用該方法時,返回的值就是下面這行代碼返回的內容
return "傳入的參數爲:" + arguments[0].toString();
}
});
// 獲取到的內容object爲上面定義的返回內容
Object object = mock.get("abcd");
System.out.println(object);
// 驗證通過
verify(mock).get(anyString());
}
//doThrow(),doAnswer(),doNothing(),doReturn() 和doCallRealMethod()的另一種編碼方式
@Test
public void test09() {
List mock = mock(List.class);
//之前我們使用doReturn doAnswer等方法格式是when(mock.get(x)).thenXxxxx(),當然也可有用另一種編碼風格書寫
doThrow(new RuntimeException()).when(mock).get(1);
doReturn("abcd").when(mock).get(2);
//兩種方式,個人更喜歡前面一種方式,因爲語義更加通順。
}
//調用模擬對象的實際的方法
@Test
public void test10(){
Person mock = mock(Person.class);
when(mock.method()).thenCallRealMethod();
String method = mock.method();
System.out.println(method);
method = mock.method();
System.out.println(method);
}
//當調用方法的時候,什麼事情都不做,但是注意,該doNothing只能調用返回值void的方法,否則失敗。
//如下案例,第一次調用該方法,什麼操作都沒有,第二次調用的話,就拋出異常。
@Test
public void test11(){
Person mock = mock(Person.class);
doNothing().doThrow(new RuntimeException()).when(mock).method2();
mock.method2();
System.out.println("第二次執行了");
mock.method2();
}
//監視真實的對象,之前創建一個mock模擬對象,我們都是通過接口或者其本類直接生成一個模擬對象,現在直接通過實際存在的對象,創建一個mock模擬對象
@Test
public void test12(){
//創建一個真實存在的對象
ArrayList<String> arrayList = new ArrayList<String>();
ArrayList<String> spy = spy(arrayList);
//依然可以通過這種方式設置調用方法返回的存根
when(spy.size()).thenReturn(100);
System.out.println(spy.size());
//當然,也可以像使用真實的對象那樣,使用這個監視對象spy
spy.add("第1個參數");
spy.add("第2個參數");
spy.add("第3個參數");
System.out.println(spy.get(0));
System.out.println(spy.get(1));
System.out.println(spy.get(2));
}
//當監視真實對象時,設置存根注意事項
@Test
public void test13(){
ArrayList<String> arrayList = new ArrayList<String>();
ArrayList<String> spy = spy(arrayList);
//如下設置了存根,再調用方法的時候,就會報錯,數組角標越界
// when(spy.get(0)).thenReturn("abcd");
// spy.get(0);
//使用另一種方式實現即可
doReturn("abcd").when(spy).get(0);
String string = spy.get(0);
System.out.println(string);
}
//參數捕獲器,當我們驗證方法的時候,在驗證時候,可以使用參數捕獲器捕獲到調用方法傳入的參數
@Test
public void test14(){
List mock1 = mock(List.class);
List mock2 = mock(List.class);
//在之前的情況,我們發現給模擬對象存入值,再取出來的話,壓根就沒存入,如下案例:
mock1.add("abcd");
//結果是一個空
System.out.println(mock1.get(0));
//那麼,怎麼樣可以捕獲到這個add添加的參數呢?這裏用到參數捕獲器
//創建參數捕獲器
ArgumentCaptor<List> ac = ArgumentCaptor.forClass(List.class);
//在驗證的時候,進行捕獲,調用add方法,傳入的參數進行捕獲
verify(mock1).add(ac.capture());
//捕獲以後,看一下捕獲到的內容是否是我們之前設置的
System.out.println(ac.getValue());
//如果調用了2次add方法,那麼就如下方式捕獲
mock2.add("AAAA");
mock2.add("BBBB");
verify(mock2 , times(2)).add(ac.capture());
for (Object object : ac.getAllValues().toArray()) {
System.out.println(object );
}
//如果傳入多個參數,如下案例:
Person mock = mock(Person.class);
mock.method3("str1", "str2");
ArgumentCaptor<String> ac2 = ArgumentCaptor.forClass(String.class);
verify(mock).method3(ac2.capture(),ac2.capture());
for (Object object : ac2.getAllValues().toArray()) {
System.out.println(object);
}
}
}
class Person{
public String method(){
return "Person";
}
void method2(){
System.out.println("method2");
}
void method3(String str1,String str2){}
}