Android单元测试-Mockito的使用

申明依赖

testCompile"junit:junit:$rootProject.ext.junitVersion" testCompile"org.mockito:mockitoall:$rootProject.ext.mockitoVersion"
测试代码写在test 文件夹下
如果是这样:
androidTestCompile"junit:junit:$rootProject.ext.junitVersion" androidTestCompile "org.mockito:mockito-core:$rootProject.ext.mockitoVersion"
测试代码写在androidTest文件夹下

mockitoVersion = '1.10.19'```
###创建Mockito

@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}```

验证行为
method:
  • Mockito.verify() :验证Mock对象的方法是否被调用。
  • Mockito.times() :调用Mock对象的方法次数。
  • Mockito.atMost(count) , Mockito.atLeast(count) , Mockito.never() :最多次数,最少次数,永远调用。
  • Mockito.anyInt() , Mockito.anyLong() , Mockito.anyDouble()等等 : 参数设置-任意的Int类型,任意的Long类型。。。等。
//Let's import Mockito statically so that the code looks clearer 
import static org.mockito.Mockito.*; 
//mock creation 
List mockedList = mock(List.class); 
//using mock object
 mockedList.add("one");
 mockedList.clear(); 
//verification 
verify(mockedList).add("one"); 
verify(mockedList).clear();```
解读:
          *1*.mock一个List的到测试对象 
          *2*.verify(Object)调用add方法
          *3*.可以验证是否调用add方法是否被调用
>下面的描述来自 [Mock以及Mockito的使用](http://chriszou.com/2016/04/29/android-unit-testing-mockito.html)
>
```Mockito.verify(mockUserManager).performLogin("xiaochuang", "xiaochuang password");```
验证mockUserManager的performLogin()得到了调用,同时参数是“xiaochuang”和"xiaochuang password"。其实更准确的说法是,这行代码验证的是,mockUserManager的performLogin()方法得到了**一次**调用。因为这行代码其实是:
```Mockito.verify(mockUserManager,Mockito.times(1)).performLogin("xiaochuang","xiaochuang password");```
的简写,或者说重载方法,注意其中的`Mockito.times(1)`。因此,如果你想验证一个对象的某个方法得到了多次调用,只需要将次数传给`Mockito.times()`就好了。```Mockito.verify(mockUserManager,Mockito.times(3)).performLogin(...); 
//验证mockUserManager的performLogin得到了三次调用。```
对于调用次数的验证,除了可以验证固定的多少次,还可以验证最多,最少从来没有等等,方法分别是:atMost(count), atLeast(count), never()等等,都是Mockito的静态方法,其实大部分时候我们会static import Mockito这个类的所有静态方法,这样就不用每次加上Mockito.前缀了。本文下面我也按照这个规则。(其实我早就想说这句话啦,只是一直没找到好的时机[喜极而泣])
很多时候你并不关心被调用方法的参数具体是什么,或者是你也不知道,你只关心这个方法得到调用了就行。这种情况下,Mockito提供了一系列的any方法,来表示任何的参数都行:
**Mockito.verify(mockUserManager).performLogin(Mockito.anyString(),Mockito.anyString());**
**anyString(**)表示任何一个字符串都可以。null?也可以的!类似anyString,还有**anyInt, anyLong, anyDouble**等等。**anyObject**表示任何对象,**any(clazz)**表示任何属于clazz的对象。在写这篇文章的时候,我刚刚发现,还有非常有意思也非常人性化的**anyCollection,anyCollectionOf(clazz), anyList(Map, set), anyListOf(clazz)等等**。

#### 指定mock对象的某些方法的行为

LinkedList mockedList = mock(LinkedList.class);
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(1)).thenThrow(new RuntimeException());```
解读:

  • 默认情况下,所有方法都会返回值,一个 mock 将返回要么 null,一个原始/基本类型的包装值或适当的空集。例如,对于一个 int/Integer 就是 0,而对于 boolean/Boolean 就是 false。
  • 默认值 可以被覆盖。
  • 一旦 指定值,该方法将始终返回指定值,无论它有多少次被调用。
    如 when(mockedList.get(0)).thenReturn("first"); 调用mockedList.get(0)的时候,因为指定了返回值 thenReturn("first"); 所以返回的值就是指定值 first

怎么样指定一个方法执行特定的动作

下文均摘自 Mock以及Mockito的使用

  • doNothing() doAnswer() doThrow()

这个功能一般是用在目标的方法是void类型的时候。现在假设我们的LoginPresenter的login()方法是这样的:

public void loginCallbackVersion(String username, String password) { 
    if (username == null || username.length() == 0) return; 
    //假设我们对密码强度有一定要求,使用一个专门的validator来验证密码的有效性 
    if (mPasswordValidator.verifyPassword(password)) return; 
    //login的结果将通过callback传递回来。
     mUserManager.performLogin(username, password, new  NetworkCallback() { 
          @Override public void onSuccess(Object data) { 
              //update view with data
           }
           @Override public void onFailure(int code, String msg) {
             //show error msg
           }
     });
}

在这里,我们想进一步测试传给mUserManager.performLogin的NetworkCallback里面的代码,验证view得到了更新等等。在测试环境下,我们并不想依赖mUserManager.performLogin的真实逻辑,而是让mUserManager直接调用传入的NetworkCallback的onSuccess或onFailure方法。这种指定mock对象执行特定的动作的写法如下:
Mockito.doAnswer(desiredAnswer).when(mockObject).targetMethod(args);
传给doAnswer()的是一个Answer对象,我们想要执行什么样的动作,就在这里面实现。结合上面的例子解释:

Mockito.doAnswer(new Answer() {
     @Override
     public Object answer(InvocationOnMock invocation) throws Throwable { 
    //这里可以获得传给performLogin的参数 
    Object[] arguments = invocation.getArguments(); //callback是第三个参数
    NetworkCallback callback = (NetworkCallback) arguments[2]; 
    callback.onFailure(500, "Server error"); 
    return 500; 
    }
}).when(mockUserManager).performLogin(anyString(), anyString(),any(NetworkCallback.class));```
这里,当调用mockUserManager的performLogin方法时,会执行answer里面的代码,我们上面的例子是直接调用传入的callback的onFailure方法,同时传给onFailure方法500和"Server error"。
当然,使用Mockito.doAnswer()需要创建一个Answer对象,这有点麻烦,代码看起来也繁琐,如果想简单的指定目标方法“什么都不做”,那么可以使用Mockito.doNothing()。如果想指定目标方法“抛出一个异常”,那么可以使用Mockito.doThrow(desiredException)。如果你想让目标方法调用真实的逻辑,可以使用Mockito.doCallRealMethod()。(什么??? 默认不是会这样吗??? No! )

####Spy
Mock对象只能调用stubbed方法,调用不了它真实的方法。但Mockito可以监视一个真实的对象,这时对它进行方法调用时它将调用真实的方法,同时也可以stubbing这个对象的方法让它返回我们的期望值。另外不论是否是真实的方法调用都可以进行verify验证。和创建mock对象一样,对于final类、匿名类和Java的基本类型是无法进行spy的。 
**监视对象** 
监视一个对象需要调用spy(T object)方法,如:List spy = spy(new LinkedList());那么spy变量就在监视LinkedList实例。
 **被监视对象的Stubbing** 
stubbing被监视对象的方法时要慎用when(Object),如:  

List spy = spy(new LinkedList());
//Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty)
when(spy.get(0)).thenReturn("foo");
//You have to use doReturn() for stubbing
doReturn("foo").when(spy).get(0);

当调用when(spy.get(0)).thenReturn("foo")时,会调用真实对象的get(0),由于list是空的所以会抛出IndexOutOfBoundsException异常,用doReturn可以避免这种情况的发生,因为它不会去调用get(0)方法。 
下面是官方文档给出的例子: 

@Test
public void spyTest2() {

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

//optionally, you can stub out some methods:  
when(spy.size()).thenReturn(100);  

//using the spy calls real methods  
spy.add("one");  
spy.add("two");  

//prints "one" - the first element of a list  
System.out.println(spy.get(0));  

//size() method was stubbed - 100 is printed  
System.out.println(spy.size());  

//optionally, you can verify  
verify(spy).add("one");  
verify(spy).add("two");   

} ```

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容