Mockito框架学习

Mockito框架学习

在做单元测试的时候,有的时候用到的一些类,我们构造起来不是那么容易,比如HttpRequest,或者说某个Service依赖到了某个Dao,想构造service还得先构造dao,这些外部对象构造起来比较麻烦。 所以出现了Mock! 我们可以用 Mock 工具来模拟这些外部对象,来完成我们的单元测试。
关于什么时候需要Mock对象,Tim Mackinnon给我们了一些建议:
  ----- 真实对象具有不可确定的行为(产生不可预测的结果,如股票的行情)
  ----- 真实对象很难被创建(比如具体的web容器)
  ----- 真实对象的某些行为很难触发(比如网络错误)
  ----- 真实情况令程序的运行速度很慢
  ----- 真实对象有用户界面
  ----- 测试需要询问真实对象它是如何被调用的(比如测试可能需要验证某个回调函数是否被调用了)
----- 真实对象实际上并不存在(当需要和其他开发小组,或者新的硬件系统打交道的时候,这是一个普遍的问题)
Mock的概念,其实很简单,我们前面也介绍过:所谓的mock就是创建一个类的虚假的对象,在测试环境中,用来替换掉真实的对象,以达到两大目的:
1、验证这个对象的某些方法的调用情况,调用了多少次,参数是什么等等
2、指定这个对象的某些方法的行为,返回特定的值,或者是执行特定的动作

一、导入依赖

使用Mockito框架需要搭配junit单元测试框架一起使用。
Mockito框架学习

二、如何通过Mockito框架Mock一个对象

模拟一个数据库不可用的场景:
Mockito框架学习
如果正常调用,将会抛出异常:
Mockito框架学习
Mockito框架学习
此时,我们就可是使用Mockito框架mock一个对象

1、使用RunWith的初始化方式

(1)、使用Mockito.mock()的方式
Mockito框架学习
注意:Mockito.mock()并不是mock一整个类,而是根据传进去的一个类,mock出属于这个类的一个对象,并且返回这个mock对象;而传进去的这个类本身并没有改变,用这个类new出来的对象也没有受到任何改变!
Mockito框架学习
通过结果可以发现,此时就没有异常,得到的结果是null。为什么是null呢?究其原因,当我们没有对mock出来的对象的行为做stubbing操作时,调用该mock对象的方法时,将会有默认的返回值,从源码中得知返回值为null,相当于:
Mockito框架学习
(2)、使用@Mock注解的方式
Mockito支持对变量进行注解,例如将mock对象设为测试类的属性,然后通过注解的方式@Mock来定义它,这样有利于减少重复代码,增强可读性,易于排查错误等。
在单元测试中使用@Mock, @Spy, @InjectMocks等注解时,需要进行初始化后才能使用。

@RunWith(MockitoJUnitRunner.class)是一种初始化形式。

除了支持@Mock,Mockito支持的注解还有@Spy(监视真实的对象),@Captor(参数捕获器),@InjectMocks(mock对象自动注入)。
Mockito框架学习

2、使用Annotation的初始化的方式

若在单元测试类中使用了@RunWith(SpringJUnit4ClassRunner.class) 就不能再使用RunWith(MockitoJUnitRunner.class),可以使用 MockitoAnnotations.initMocks(this) 来代替。一般情况下在Junit4的@Before定义的方法中执行初始化工作,如下:
Mockito框架学习

3、使用Rule的初始化方式

Mockito框架学习
同样的,使用Mockito.mock()的方式也是一样的:
Mockito框架学习
Mockito框架学习

三、Stubbling(打桩)语法

stub打桩测试,就是通过触发定义好的stub描述条件,返回需要我们的行为。

1、when().thenReturn() : 指定方法的特定行为,返回预期值:

Mockito框架学习
如果不指定,一个mock对象的所有非void方法都将返回默认值:int、long类型方法将返回0,boolean方法将返回false,对象方法将返回null等等;而void方法将什么都不做。

Mockito框架学习

2、when().thenThrow() : 指定方法的特定行为,抛出异常

(1)、通过try-catch并且最后使用fail()的方式;
Mockito框架学习
(2)、使用@Test和其expected属性
Mockito框架学习

3.doNothing().when(): void方法没有返回值时使用

当一个方法有返回值的时候,我们可以使用断言的方式来确定方法是否被调用,那么如果方法没有返回值,是void的时候,怎么确定我们stub的方法是否被执行呢?使用Mockito,验证验证一个对象的方法调用情况的姿势是:
Mockito.verify(objectToVerify).methodToVerify(arguments);
其中,objectToVerify和methodToVerify分别是你想要验证的对象和方法,arguments是方法的参数。

比如: Mockito.verify(list).get(0);
Mockito框架学习
也可以添加Mockito.tims()用于验证该方法被调用了多少次,如果不添加times,默认是1次。
注意:Mockito.verify()的参数必须是mock对象,也就是说,Mockito只能验证mock对象的方法调用情况,否则将报错。

4、doThrow().when(): 调用void方法抛出异常时使用

Mockito框架学习

5、doReturn().when()

doReturn(…).when(…)和when(…).thenReturn(…)。这两个方法在大部分情况下都是可以相互替换的,但是在使用了Spies对象(@Spy注解),而不是mock对象(@Mock注解)的情况下他们调用的结果是不相同的(目前我只知道这一种情况,可能还有别的情形下是不能相互替换的)。
● when(…) thenReturn(…)会调用真实的方法,如果你不想调用真实的方法而是想要mock的话,就不要使用这个方法。
● doReturn(…) when(…) 不会调用真实方法
以下情况是等价的
Mockito框架学习

6、关于Stubbling的迭代

(1)让某个方法在执行不同次数的时候返回不同的值,使用如下:
Mockito框架学习
等价于:
Mockito框架学习
若是以下情况:
Mockito框架学习

7、doAnswer接口
8、thenCallRealMethod

Mockito框架学习
对比,首先是通过thenReturn(),
Mockito框架学习
Mockito框架学习
由此可以看出,通过mock生成的对象是Mockito框架给我们生成的CGLB代理对象,我们使用这个对象调用它的方法,如若已经stub,则返回预期的值,若没有stub,将返回该方法返回值的默认值。
接下来通过thenCallRealMethod方法,
Mockito框架学习
Mockito框架学习

四、Mockito验证方法的调用

前面我们讲了验证一个对象的某个method得到调用的方法,比如:
Mockito框架学习
这句话的作用是,验证的userDao的findUse()得到了调用,同时参数是“zs”和"123"。其实更准确的说法是,这行代码验证的是,userDao的findUser()方法得到了一次调用。因为这行代码其实是:
Mockito框架学习
的简写,或者说重载方法,注意其中的Mockito.times(1)。
因此,如果你想验证一个对象的某个方法得到了多次调用,只需要将次数传给Mockito.times()就好了。
Mockito框架学习
验证的userDao的findUse()调用了3次。
对于调用次数的验证,除了可以验证固定的多少次,还可以验证最多,最少从来没有等等,方法分别是:atMost(count), atLeast(count), never()等等,都是Mockito的静态方法,其实大部分时候我们会static import Mockito这个类的所有静态方法,这样就不用每次加上Mockito.前缀了。

Mockito框架学习
Mockito框架学习
Mockito框架学习

五、Mockito参数匹配

很多时候你并不关心被调用方法的参数具体是什么,或者是你也不知道,你只关心这个方法得到调用了就行。这种情况下,Mockito提供了一系列的any方法,来表示任何的参数都行:
Mockito框架学习
简写:
Mockito框架学习
anyString()表示任何一个字符串都可以。null也可以的!
类似anyString,还有anyInt, anyLong, anyDouble等等。anyObject表示任何对象,any(clazz)表示任何属于clazz的对象。还有非常有意思也非常人性化的anyCollection,anyCollectionOf(clazz), anyList(Map, set), anyListOf(clazz)等等。