简介
Mockito是一个流行的Mocking框架。它使用起来简单,学习成本很低,而且具
有非常简洁的API,测试代码的可读性很高。因此它十分受欢迎,用户群越来越
多,很多的开源的软件也选择了Mockito。
要想了解更多有关Mockito的信息,请访问它的官方网站:http://mockito.org/
Stub 和Mock
在开始使用Mockito之前,先简单的了解一下Stub和Mock的区别。
Stub对象用来提供测试时所需要的测试数据,可以对各种交互设置相应的回应。
例如我们可以设置方法调用的返回值等等。Mockito中when(…).thenReturn(…)
这样的语法便是设置方法调用的返回值。另外也可以设置方法在何时调用会抛异
常等。
Mock对象用来验证测试中所依赖对象间的交互是否能够达到预期。Mockito中用
verify(…).methodXxx(…) 语法来验证 methodXxx 方法是否按照预期进行了调
用。
有关stub和mock的详细论述见,Martin Fowler文章《Mocks Aren't Stub》
http://martinfowler.com/articles/mocksArentStubs.html
在 Mocking 框架中所谓的mock 对象实际上是作为上述的stub 和mock 对象同时
使用的。因为它既可以设置方法调用返回值,又可以验证方法的调用。
Mockito 的获取
Jar 包的获取
可以访问下面的链接来下载最新的Jar包,笔者使用的当前最新版为:1.8.5
http://code.google.com/p/mockito/downloads/list
Maven
如果项目是通过Maven管理的,需要在项目的Pom.xml中增加如下的依赖:
从一个实例开始
Mocktio包的引入
在程序中可以import org.mockito.Mockito;然后调用它的static方法,或者
import static org.mockito.Mockito.*;个人倾向于后者,因为这样可以更方
便些。
一个简单的例子
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.util.Iterator;
import org.junit.Test;
/**
*
* @author Brian Zhao
*/
public class SimpleTest {
@Test
public void simpleTest(){
//arrange
Iterator i=mock(Iterator.class);
when(i.next()).thenReturn("Hello").thenReturn("World");
//act
String result=i.next()+" "+i.next();
//verify
verify(i, times(2)).next();
//assert
assertEquals("Hello World", result);
}
}
在上面的例子中包含了Mockito的基本功能:
创建 Mock 对象
创建Mock对象的语法为,mock(class or interface)。例子中创建了Iterator
接口的mock对象。
设置方法调用的预期返回
通过when(mock.someMethod()).thenReturn(value) 来设定mock对象某个方
法调用时的返回值。例子中我们对Iterator接口的next()方法调用进行了预期
设定,当调用next()方法时会返回”Hello”,由于连续设定了返回值,因此当第
二次调用时将返回”World”。
验证方法调用
接下来对mock对象的next()方法进行了一系列实际的调用。mock对象一旦建
立便会自动记录自己的交互行为,所以我们可以有选择的对它的交互行为进行验
证。在Mockito中验证mock对象交互行为的方法是
verify(mock).someMethod(…)。于是用此方法验证了next()方法调用,因为调
用了两次,所以在verify中我们指定了times参数(times的具体应用在后面
会继续介绍)。最后assert返回值是否和预期一样。
Mock对象的创建和Stubbing
Mock 对象的创建
mock(Class
mock(Class
可以对类和接口进行mock 对象的创建,创建的时候可以为mock 对象命名,也
可以忽略命名参数。为mock 对象命名的好处就是调试的时候会很方便,比如,
我们mock 多个对象,在测试失败的信息中会把有问题的mock 对象打印出来,
有了名字我们可以很容易定位和辨认出是哪个mock对象出现的问题。另外它也
有限制,对于final类、匿名类和Java的基本类型是无法进行mock的。
Mock 对象的期望行为及返回值设定
我们已经了解到可以通过when(mock.someMethod()).thenReturn(value) 来
设定mock对象的某个方法调用时的返回值,但它也同样有限制对于static和final
修饰的方法是无法进行设定的。下面来详细的介绍一下有关方法及返回值的设定:
首先假设我们创建Iterator接口的mock对象
Iterator
对方法设定返回值
when(i.next()).thenReturn("Hello")
对方法设定返回异常
when(i.next()).thenThrow(new RuntimeException())
Mockito支持迭代风格的返回值设定
第一种方式
when(i.next()).thenReturn("Hello").thenReturn("World")
第二种方式
when(i.next()).thenReturn("Hello", "World")
上面的设定相当于:
when(i.next()).thenReturn("Hello")
when(i.next()).thenReturn("World")
第一次调用i.next()将返回”Hello”,第二次的调用会返回”World”。
Stubbing的另一种语法
doReturn(Object) 设置返回值
doReturn("Hello").when(i).next();
迭代风格
doReturn("Hello").doReturn("World").when(i).next();
返回值的次序为从左至右,第一次调用返回”Hello”,第二次返回”World”。
doThrow(Throwable) 设置返回异常
doThrow(new RuntimeException()).when(i).next();
因为这种语法的可读性不如前者,所以能使用前者的情况下尽量使用前者,当然
在后面要介绍的Spy除外。
对 void 方法进行方法预期设定
void方法的模拟不支持when(mock.someMethod()).thenReturn(value)这样的
语法,只支持下面的方式:
doNothing() 模拟不做任何返回(mock对象void方法的默认返回)
doNothing().when(i).remove();
doThrow(Throwable) 模拟返回异常
doThrow(new RuntimeException()).when(i).remove();
迭代风格
doNothing().doThrow(new RuntimeException()).when(i).remove();
第一次调用remove方法什么都不做,第二次调用抛出RuntimeException异常。
Argument Matcher(参数匹配器)
Mockito通过equals()方法,来对方法参数进行验证。但有时我们需要更加灵活的
参数需求,比如,匹配任何的String类型的参数等等。参数匹配器就是一个能够
满足这些需求的工具。
Mockito框架中的Matchers 类内建了很多参数匹配器,而我们常用的Mockito对
象便是继承自Matchers。这些内建的参数匹配器如,anyInt()匹配任何int类型参
数,anyString()匹配任何字符串,anySet()匹配任何Set 等。下面通过例子来说明
如何使用内建的参数匹配器:
@Test
public void argumentMatchersTest(){
List
when(mock.get(anyInt())).thenReturn("Hello").thenReturn("World
");
String result=mock.get(100)+" "+mock.get(200);
verify(mock,times(2)).get(anyInt());
assertEquals("Hello World",result);
}
Stubbing时使用内建参数匹配器
例子中,首先mock 了List 接口,然后用迭代的方式模拟了get 方法的返回值,
这里用了anyInt()参数匹配器来匹配任何的int 类型的参数。所以当第一次调用
get方法时输入任意参数为100方法返回”Hello”,第二次调用时输入任意参数200
返回值”World”。
Verfiy时使用参数匹配器
最后进行verfiy 验证的时候也可将参数指定为anyInt()匹配器,那么它将不关心
调用时输入的参数的具体参数值。
注意事项
如果使用了参数匹配器,那么所有的参数需要由匹配器来提供,否则将会报错。
假如我们使用参数匹配器stubbing 了mock 对象的方法,那么在verify 的时候也
需要使用它。如:
@Test
public void argumentMatchersTest(){
Map mapMock = mock(Map.class);
when(mapMock.put(anyInt(), anyString())).thenReturn("world");
mapMock.put(1, "hello");
verify(mapMock).put(anyInt(), eq("hello"));
}
在最后的验证时如果只输入字符串”hello”是会报错的,必须使用Matchers 类内
建的eq方法。如果将anyInt()换成1进行验证也需要用eq(1)。
详细的内建参数匹配器请参考:
http://docs.mockito.googlecode.com/hg/org/mockito/Matchers.html
Mock对象的行为验证
之前介绍了如何设置mock对象预期调用的方法及返回值。下面介绍方法调用的
验证,而它关注点则在mock 对象的交互行为上,比如验证mock 对象的某个方
法调用参数,调用次数,顺序等等。下面来看例子:
@Test
public void verifyTestTest() {
List
List
when(mock.get(0)).thenReturn("hello");
mock.get(0);
mock.get(1);
mock.get(2);
mock2.get(0);
verify(mock).get(2);
verify(mock, never()).get(3);
verifyNoMoreInteractions(mock);
verifyZeroInteractions(mock2);
}
验证的基本方法
我们已经熟悉了使用verify(mock).someMethod(…)来验证方法的调用。例子中,
我们mock 了List 接口,然后调用了mock 对象的一些方法。验证是否调用了
mock.get(2)方法可以通过verify(mock).get(2)来进行。verify 方法的调用不
关心是否模拟了get(2)方法的返回值,只关心mock 对象后,是否执行了
mock.get(2),如果没有执行,测试方法将不会通过。
验证未曾执行的方法
在verify方法中可以传入never()方法参数来确认mock.get(3)方法不曾被执行过。
另外还有很多调用次数相关的参数将会在下面提到。
查询多余的方法调用
verifyNoMoreInteractions()方法可以传入多个mock对象作为参数,用来验证传入
的这些mock 对象是否存在没有验证过的调用方法。本例中传入参数mock,测
试将不会通过,因为我们只verify了mock对象的get(2)方法,没有对get(0)和get(1)
进行验证。为了增加测试的可维护性,官方不推荐我们过于频繁的在每个测试方
法中都使用它,因为它只是测试的一个工具,只在你认为有必要的时候才用。
查询没有交互的mock对象
verifyZeroInteractions()也是一个测试工具,源码和verifyNoMoreInteractions()的实
现是一样的,为了提高逻辑的可读性,所以只不过名字不同。在例子中,它的目
的是用来确认mock2对象没有进行任何交互,但mock2执行了get(0)方法,所以
这里测试会报错。由于它和verifyNoMoreInteractions()方法实现的源码都一样,
因此如果在verifyZeroInteractions(mock2)执行之前对mock.get(0)进行了
验证那么测试将会通过。
对 Mock对象方法的调用次数、顺序和超时进行验证
验证方法调用的次数
如果要验证Mock 对象的某个方法调用次数,则需给verify 方法传入相关的验证
参数,它的调用接口是verify(T mock, VerificationMode mode) 。如:
verify(mock,times(3)).someMethod(argument) 验证mock 对象
someMethod(argument)方法是否调用了三次。times(N)参数便是验证调用次数的
参数,N 代表方法调用次数。其实verify 方法中如果不传调用次数的验证参数,
它默认传入的便是times(1),即验证mock 对象的方法是否只被调用一次,如果
有多次调用测试方法将会失败。
Mockito除了提供times(N)方法供我们调用外,还提供了很多可选的方法:
never() 没有被调用,相当于times(0)
atLeast(N) 至少被调用N次
atLeastOnce() 相当于atLeast(1)
atMost(N) 最多被调用N次
超时验证
Mockito 提供对超时的验证,但是目前不支持在下面提到的顺序验证中使用。进
行超时验证和上述的次数验证一样,也要在verify 中进行参数的传入,参数为
timeout(int millis),timeout方法中输入的是毫秒值。下面看例子:
验证someMethod()是否能在指定的100毫秒中执行完毕
verify(mock, timeout(100)).someMethod();
结果和上面的例子一样,在超时验证的同时可进行调用次数验证,默认次数为1
verify(mock, timeout(100).times(1)).someMethod();
在给定的时间内完成执行次数
verify(mock, timeout(100).times(2)).someMethod();
给定的时间内至少执行两次
verify(mock, timeout(100).atLeast(2)).someMethod();
另外timeout也支持自定义的验证模式,
verify(mock, new Timeout(100,
yourOwnVerificationMode)).someMethod();
验证方法调用的顺序
Mockito 同样支持对不同Mock 对象不同方法的调用次序进行验证。进行次序验
证是,我们需要创建InOrder对象来进行支持。例:
创建 mock对象
List
List
调用mock对象方法
firstMock.add("was called first");
firstMock.add("was called first");
secondMock.add("was called second");
secondMock.add("was called third");
创建InOrder 对象
inOrder方法可以传入多个mock对象作为参数,这样便可对这些mock对象的方
法进行调用顺序的验证InOrder inOrder = inOrder( secondMock,
firstMock );
验证方法调用
接下来我们要调用InOrder对象的verify方法对mock方法的调用顺序进行验证。
注意,这里必须是你对调用顺序的预期。
InOrder对象的verify方法也支持调用次数验证,上例中,我们期望
firstMock.add("was called first")方法先执行并执行两次,所以进行了下
面的验证inOrder.verify(firstMock,times(2)).add("was called first")。
其次执行了secondMock.add("was called second")方法,继续验证此方法的
执行inOrder.verify(secondMock).add("was called second")。如果mock
方法的调用顺序和InOrder中verify的顺序不同,那么测试将执行失败。
InOrder的verifyNoMoreInteractions()方法
它用于确认上一个顺序验证方法之后,mock 对象是否还有多余的交互。它和
Mockito提供的静态方法verifyNoMoreInteractions 不同,InOrder的验证是基于顺
序的,另外它只验证创建它时所提供的mock 对象,在本例中只对firstMock 和
secondMock有效。例如:
inOrder.verify(secondMock).add("was called second");
inOrder.verifyNoMoreInteractions();
在验证secondMock.add("was called second")方法之后,加上InOrder的
verifyNoMoreInteractions方法,表示此方法调用后再没有多余的交互。例子
中会报错,因为在此方法之后还执行了secondMock.add("was called third")。
现在将上例改成:
inOrder.verify(secondMock).add("was called third");
inOrder.verifyNoMoreInteractions();
测试会恢复为正常,因为在secondMock.add("was called third")之后已经没
有多余的方法调用了。如果这里换成Mockito类的verifyNoMoreInteractions方法测
试还是会报错,它查找的是mock对象中是否存在没有验证的调用方法,和顺序
是无关的。
Mock对象的重置
Mockito提供了reset(mock1,mock2……)方法,用来重置mock对象。当mock对象
被重置后,它将回到刚创建完的状态,没有任何stubbing和方法调用。这个特性
平时是很少用到的,因为我们大都为每个test 方法创建mock,所以没有必要对
它进行重置。官方提供这个特性的唯一目的是使得我们能在有容器注入的mock
对象中工作更为方便。所以,当决定要使用这个方法的时候,首先应该考虑一下
我们的测试代码是否简洁和专注,测试方法是否已经超长了。
Answer接口(方法预期回调接口)的应用
Answer接口说明
对mock对象的方法进行调用预期的设定,可以通过thenReturn()来指定返回值,
thenThrow()指定返回时所抛异常,通常来说这两个方法足以应对一般的需求。但
有时我们需要自定义方法执行的返回结果,Answer 接口就是满足这样的需求而
存在的。另外,创建mock 对象的时候所调用的方法也可以传入Answer 的实例
mock(java.lang.Class
些mock对象没有stubbing的方法的返回值。
InvocationOnMock 对象的方法
Answer 接口定义了参数为InvocationOnMock 对象的answer 方法,利用
InvocationOnMock提供的方法可以获取mock 方法的调用信息。下面是它提供的
方法:
getArguments() 调用后会以Object数组的方式返回mock方法调用的参数。
getMethod() 返回java.lang.reflect.Method 对象
getMock() 返回mock对象
callRealMethod() 真实方法调用,如果mock的是接口它将会抛出异常
通过一个例子来看一下Answer 的使用。我们自定义CustomAnswer 类,它实现
了Answer接口,返回值为String类型。
public class CustomAnswer implements Answer
public String answer(InvocationOnMock invocation) throws
Throwable {
Object[] args = invocation.getArguments();
Integer num = (Integer)args[0];
if( num>3 ){
return "yes";
} else {
throw new RuntimeException();
}
}
}
这个返回值是这样的逻辑,如果调用mock某个方法输入的参数大于3返回”yes”,
否则抛出异常。
Answer接口的使用
应用方式如下:
首先对List接口进行mock
List
指定方法的返回处理类CustomAnswer,因为参数为4大于3所以返回字符串”yes”
when(mock.get(4)).thenAnswer(new CustomAnswer());
另外一种方式
doAnswer(new CustomAnswer()).when(mock.get(4));
对void方__________法也可以指定Answer来进行返回处理,如:
doAnswer(new xxxAnswer()).when(mock).clear();
当设置了Answer后,指定方法的调用结果就由我们定义的Answer接口来处理了。
另外我们也可以使用匿名内部类来进行应用:
@Test
public void customAnswerTest(){
List
when(mock.get(4)).thenAnswer(new Answer(){
public String answer(InvocationOnMock invocation) throws
Throwable {
Object[] args = invocation.getArguments();
Integer num = (Integer)args[0];
if( num>3 ){
return "yes";
} else {
throw new RuntimeException();
}
}
});
System.out.println(mock.get(4));
}
自定义参数匹配器
Mockito参数匹配器的实现使用了Hamcrest框架(一个书写匹配器对象时允许直
接定义匹配规则的框架,网址:http://code.google.com/p/hamcrest/)。它已经提供了
许多规则供我们使用, Mockito在此基础上也内建了很规则。但有时我们还是需
要更灵活的匹配,所以需要自定义参数匹配器。
ArgumentMatcher 抽象类
自定义参数匹配器的时候需要继承ArgumentMatcher抽象类,它实现了Hamcrest
框架的Matcher接口,定义了describeTo方法,所以我们只需要实现matches 方
法在其中定义规则即可。
下面自定义的参数匹配器是匹配size大小为2 的List:
class IsListOfTwoElements extends ArgumentMatcher {
public boolean matches(Object list) {
return ((List) list).size() == 2;
}
}
@Test
public void argumentMatchersTest(){
List mock = mock(List.class);
when(mock.addAll(argThat(new
IsListOfTwoElements()))).thenReturn(true);
mock.addAll(Arrays.asList("one", "two", "three"));
verify(mock).addAll(argThat(new IsListOfTwoElements()));
}
argThat(Matcher
Matcher 接口的实现类。上例中在stubbing 和verify addAll 方法时通过
argThat(Matcher
IsListOfTwoElements 用来匹配size 大小为2 的List。因为例子中传入List
的元素为三个,所以测试将失败。
较复杂的参数匹配将会降低测试代码的可读性。有时实现参数对象的equals()
方法是个不错的选择(Mockito默认使用equals()方法进行参数匹配),它可以
使测试代码更为整洁。另外,有些场景使用参数捕获器(ArgumentCaptor)要比
自定义参数匹配器更加合适。
利用ArgumentCaptor(参数捕获器)捕获方法参数进行验证
在某些场景中,不光要对方法的返回值和调用进行验证,同时需要验证一系列交
互后所传入方法的参数。那么我们可以用参数捕获器来捕获传入方法的参数进行
验证,看它是否符合我们的要求。
ArgumentCaptor 介绍
通过 ArgumentCaptor 对象的forClass(Class
对象。然后便可在验证时对方法的参数进行捕获,最后验证捕获的参数值。如果
方法有多个参数都要捕获验证,那就需要创建多个ArgumentCaptor对象处理。
ArgumentCaptor的Api
argument.capture() 捕获方法参数
argument.getValue() 获取方法参数值,如果方法进行了多次调用,它将返回
最后一个参数值
argument.getAllValues() 方法进行多次调用后,返回多个参数值
应用实例
@Test
public void argumentCaptorTest() {
List mock = mock(List.class);
List mock2 = mock(List.class);
mock.add("John");
mock2.add("Brian");
mock2.add("Jim");
ArgumentCaptor argument = ArgumentCaptor.forClass(String.class);
verify(mock).add(argument.capture());
assertEquals("John", argument.getValue());
verify(mock2, times(2)).add(argument.capture());
assertEquals("Jim", argument.getValue());
assertArrayEquals(new
Object[]{"Brian","Jim"},argument.getAllValues().toArray());
}
首先构建ArgumentCaptor需要传入捕获参数的对象,例子中是String。接着要在
verify 方法的参数中调用argument.capture()方法来捕获输入的参数,之后
argument变量中就保存了参数值,可以用argument.getValue()获取。当某个
对象进行了多次调用后,如mock2 对象,这时调用argument.getValue()获取
到的是最后一次调用的参数。如果要获取所有的参数值可以调用
argument.getAllValues(),它将返回参数值的List。
在某种程度上参数捕获器和参数匹配器有很大的相关性。它们都用来确保传入
mock 对象参数的正确性。然而,当自定义的参数匹配器的重用性较差时,用参
数捕获器会更合适,只需在最后对参数进行验证即可。
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");
}
RETURNS_SMART_NULLS 和RETURNS_DEEP_STUBS
RETURNS_SMART_NULLS
RETURNS_SMART_NULLS是实现了Answer接口的对象,它是创建mock对象时的
一个可选参数,mock(Class, Answer)。在创建mock对象时,有的方法我们没有进
行stubbing,所以在调用的时候有时会返回Null这样在进行处理时就很可能抛出
NullPointerException。如果通过RETURNS_SMART_NULLS参数来创建的mock对象
在调用没有stubbed的方法时他将返回SmartNull。例如:返回类型是String 它将
返回空字符串””;是int,它将返回0;如果是List,它会返回一个空的List。另
外,在堆栈中可以看到SmartNull的友好提示。
@Test
public void returnsSmartNullsTest() {
List mock = mock(List.class, RETURNS_SMART_NULLS);
System.out.println(mock.get(0));
System.out.println(mock.toArray().length);
}
由于使用了RETURNS_SMART_NULLS 参数来创建mock 对象,所以在执行下面的
操作时将不会抛出NullPointerException 异常,另外堆栈也提示了相关的信息
“SmartNull returned by unstubbed get() method on mock”。
RETURNS_DEEP_STUBS
同上面的参数一样RETURNS_DEEP_STUBS也是一个创建mock对象时的备选参数。
例如我们有Account 对象和RailwayTicket 对象,RailwayTicket 是Account 的一个
属性。
public class Account {
private RailwayTicket railwayTicket;
public RailwayTicket getRailwayTicket() {
return railwayTicket;
}
public void setRailwayTicket(RailwayTicket railwayTicket) {
this.railwayTicket = railwayTicket;
}
}
public class RailwayTicket {
private String destination;
public String getDestination() {
return destination;
}
public void setDestination(String destination) {
this.destination = destination;
}
}
下面通过RETURNS_DEEP_STUBS来创建mock 对象。
@Test
public void deepstubsTest(){
Account account = mock(Account.class, RETURNS_DEEP_STUBS);
when(account.getRailwayTicket().getDestination()).thenReturn("
Beijing");
account.getRailwayTicket().getDestination();
verify(account.getRailwayTicket()).getDestination();
assertEquals("Beijing",
account.getRailwayTicket().getDestination());
}
上例中,我们只创建了Account 的mock 对象,没有对RailwayTicket 创建mock,
因为通过RETURNS_DEEP_STUBS参数程序会自动进行mock所需要的对象,所以
上面的例子等价于:
@Test
public void deepstubsTest2(){
Account account = mock(Account.class);
RailwayTicket railwayTicket = mock(RailwayTicket.class);
when(account.getRailwayTicket()).thenReturn(railwayTicket);
when(railwayTicket.getDestination()).thenReturn("Beijing");
account.getRailwayTicket().getDestination();
verify(account.getRailwayTicket()).getDestination();
assertEquals("Beijing",
account.getRailwayTicket().getDestination());
}
为了代码整洁和确保它的可读性,我们应该少用这个特性。
Mockito对Annotation的支持
Mockito 支持对变量进行注解,例如将mock 对象设为测试类的属性,然后通过
注解的方式@Mock 来定义它,这样有利于减少重复代码,增强可读性,易于排
查错误等。除了支持@Mock,Mockito支持的注解还有@Spy(监视真实的对象),
@Captor(参数捕获器),@InjectMocks(mock对象自动注入)。
Annotation的初始化
只有Annotation还不够,要让它们工作起来还需要进行初始化工作。初始化的方
法为:MockitoAnnotations.initMocks(testClass)参数testClass是你所写
的测试类。一般情况下在Junit4的@Before 定义的方法中执行初始化工作,如
下:
@Before
public void initMocks() {
MockitoAnnotations.initMocks(this);
}
除了上述的初始化的方法外,还可以使用Mockito 提供的Junit Runner:
MockitoJUnitRunner这样就省略了上面的步骤。
@RunWith(MockitoJUnit44Runner.class)
public class ExampleTest {
...
}
@Mock 注解
使用@Mock注解来定义mock对象有如下的优点:
1. 方便mock对象的创建
2. 减少mock对象创建的重复代码
3. 提高测试代码可读性
4. 变量名字作为mock对象的标示,所以易于排错
@Mock注解也支持自定义name 和answer属性。下面是官方给出的@Mock使用
的例子:
public class ArticleManagerTest extends SampleBaseTestCase {
@Mock
private ArticleCalculator calculator;
@Mock(name = "dbMock")
private ArticleDatabase database;
@Mock(answer = RETURNS_MOCKS)
private UserProvider userProvider;
private ArticleManager manager;
@Before
public void setup() {
manager = new ArticleManager(userProvider, database,
calculator);
}
}
public class SampleBaseTestCase {
@Before
public void initMocks() {
MockitoAnnotations.initMocks(this);
}
}
@Spy 注解
Spy的使用方法请参阅前面的章节,在此不再赘述,下面是使用方法:
public class Test{
@Spy
Foo spyOnFoo = new Foo();
@Before
public void init(){
MockitoAnnotations.initMocks(this);
}
...
}
@Captor 注解
@Captor是参数捕获器的注解,有关用法见前章,通过注解的方式也可以更便捷
的对它进行定义。使用例子如下:
public class Test {
@Captor
ArgumentCaptor
@Before
public void init() {
MockitoAnnotations.initMocks(this);
}
@Test
public void shouldDoSomethingUseful() {
// ...
verify(mock.doStuff(captor.capture()));
assertEquals("foo", captor.getValue());
}
}
@InjectMocks 注解
通过这个注解,可实现自动注入mock 对象。当前版本只支持setter 的方式进行
注入,Mockito 首先尝试类型注入,如果有多个类型相同的mock 对象,那么它
会根据名称进行注入。当注入失败的时候Mockito不会抛出任何异常,所以你可
能需要手动去验证它的安全性。
例:
@RunWith(MockitoJUnit44Runner.class)
public class ArticleManagerTest {
@Mock
private ArticleCalculator calculator;
@Mock
private ArticleDatabase database;
@Spy
private UserProvider userProvider = new ConsumerUserProvider();
@InjectMocks
private ArticleManager manager = new ArticleManager();
@Test
public void shouldDoSomething() {
manager.initiateArticle();
verify(database).addListener(any(ArticleListener.class));
}
}
上例中, ArticleDatabase 是ArticleManager 的一个属性, 由于
ArticleManager 是注解@InjectMocks 标注的,所以会根据类型自动调用它的
setter方法为它设置ArticleDatabase。
mockito api:http://docs.mockito.googlecode.com/hg-history/be6d53f62790ac7c9cf07c32485343ce94e1b563/1.9.5/org/mockito/Mockito.html
首先了解mockito 才能知道powermock,看名字就知道powermock更强,下面来介绍
下面我将以Power Mock的mockito的版本来讲述如何使用Power Mock。
测试目标类:
public class ClassUnderTest { public boolean callArgumentInstance(File file) { return file.exists(); } public boolean callInternalInstance(String path) { File file = new File(path); return file.exists(); } public boolean callFinalMethod(ClassDependency refer) { return refer.isAlive(); } public boolean callSystemFinalMethod(String str) { return str.isEmpty(); } public boolean callStaticMethod() { return ClassDependency.isExist(); } public String callSystemStaticMethod(String str) { return System.getProperty(str); } public boolean callPrivateMethod() { return isExist(); } private boolean isExist() { // do something return false; } }
依赖类:
public class ClassDependency { public static boolean isExist() { // do something return false; } public final boolean isAlive() { // do something return false; } }
接下来,对6个测试用例进行逐个的讲解。
首先需要使用@RunWith(PowerMockRunner.class)将测试用例的runner改为PowerMockRunner
1、testCallArgumentInstance:Mock参数传递的对象
@Test public void testCallArgumentInstance() { File file = PowerMockito.mock(File.class); ClassUnderTest underTest = new ClassUnderTest(); PowerMockito.when(file.exists()).thenReturn(true); Assert.assertTrue(underTest.callArgumentInstance(file)); }
需要mock的对象是由参数传进去的,这是最普通的一种mock方式,jMock,EasyMock,Mockito都能实现。
步骤:
a、通过PowerMockito.mock(File.class)创建出一个mock对象
b、然后再通过PowerMockito.when(file.exists()).thenReturn(false);来指定这个mock对象具体的行为
c、再将mock对象作为参数传递个测试方法,执行测试方法。
2、testCallInternalInstance:Mock方法内部new出来的对象
@Test @PrepareForTest(ClassUnderTest.class) public void testCallInternalInstance() throws Exception { File file = PowerMockito.mock(File.class); ClassUnderTest underTest = new ClassUnderTest(); PowerMockito.whenNew(File.class).withArguments("bbb").thenReturn(file); PowerMockito.when(file.exists()).thenReturn(true); Assert.assertTrue(underTest.callInternalInstance("bbb")); }
需要mock的对象是在方法内部new出来的,这是一种比较常见的mock方式。
步骤(已经讲过的步骤省略):
a、通过PowerMockito.whenNew(File.class).withArguments("bbb").thenReturn(file)来指定当以参数为bbb创建File对象的时候,返回已经mock的File对象。
b、在测试方法之上加注解@PrepareForTest(ClassUnderTest.class),注解里写的类是需要mock的new对象代码所在的类。
3、testCallFinalMethod:Mock普通对象的final方法。
@Test @PrepareForTest(ClassDependency.class) public void testCallFinalMethod() { ClassDependency depencency = PowerMockito.mock(ClassDependency.class); ClassUnderTest underTest = new ClassUnderTest(); PowerMockito.when(depencency.isAlive()).thenReturn(true); Assert.assertTrue(underTest.callFinalMethod(depencency)); }
Mock的步骤和之前的一样,只是需要在测试方法之上加注解@PrepareForTest(ClassDependency.class),注解里写的类是需要mock的final方法所在的类。
4、testCallStaticMethod:Mock静态方法。
@Test @PrepareForTest(ClassDependency.class) public void testCallStaticMethod() { ClassUnderTest underTest = new ClassUnderTest(); PowerMockito.mockStatic(ClassDependency.class); PowerMockito.when(ClassDependency.isExist()).thenReturn(true); Assert.assertTrue(underTest.callStaticMethod()); }
步骤:
a、通过PowerMockito.mockStatic(ClassDependency.class);表示需要mock这个类里的静态方法
b、在测试方法之上加注解@PrepareForTest(ClassDependency.class),注解里写的类是需要mock的静态方法所在的类。
5、testCallSystemStaticMethod:Mock JDK中类的静态方法。
testCallSystemFinalMethod:Mock JDK对象的final方法。
@Test @PrepareForTest(ClassUnderTest.class) public void testCallSystemStaticMethod() { ClassUnderTest underTest = new ClassUnderTest(); PowerMockito.mockStatic(System.class); PowerMockito.when(System.getProperty("aaa")).thenReturn("bbb"); Assert.assertEquals("bbb", underTest.callJDKStaticMethod("aaa")); } @Test @PrepareForTest(ClassUnderTest.class) public void testCallSystemFinalMethod() { String str = PowerMockito.mock(String.class); ClassUnderTest underTest = new ClassUnderTest(); PowerMockito.when(str.isEmpty()).thenReturn(false); Assert.assertFalse(underTest.callJDKFinalMethod(str)); }
和Mock普通对象的静态方法、final方法一样,只不过注解里写的类不一样@PrepareForTest(ClassUnderTest.class),注解里写的类是需要调用系统方法所在的类。
6、testCallPrivateMethod:Mock私有方法。
@Test @PrepareForTest(ClassUnderTest.class) public void testCallPrivateMethod() throws Exception { ClassUnderTest underTest = PowerMockito.mock(ClassUnderTest.class); PowerMockito.when(underTest.callPrivateMethod()).thenCallRealMethod(); PowerMockito.when(underTest, "isExist").thenReturn(true); Assert.assertTrue(underTest.callPrivateMethod()); }
和Mock普通方法一样,只是需要加注解@PrepareForTest(ClassUnderTest.class),注解里写的类是私有方法所在的类。
完整的测试用例类:
@RunWith(PowerMockRunner.class) public class TestClassUnderTest { @Test public void testCallArgumentInstance() { File file = PowerMockito.mock(File.class); ClassUnderTest underTest = new ClassUnderTest(); PowerMockito.when(file.exists()).thenReturn(true); Assert.assertTrue(underTest.callArgumentInstance(file)); } @Test @PrepareForTest(ClassUnderTest.class) public void testCallInternalInstance() throws Exception { File file = PowerMockito.mock(File.class); ClassUnderTest underTest = new ClassUnderTest(); PowerMockito.whenNew(File.class).withArguments("bbb").thenReturn(file); PowerMockito.when(file.exists()).thenReturn(true); Assert.assertTrue(underTest.callInternalInstance("bbb")); } @Test @PrepareForTest(ClassDependency.class) public void testCallFinalMethod() { ClassDependency depencency = PowerMockito.mock(ClassDependency.class); ClassUnderTest underTest = new ClassUnderTest(); PowerMockito.when(depencency.isAlive()).thenReturn(true); Assert.assertTrue(underTest.callFinalMethod(depencency)); } @Test @PrepareForTest(ClassUnderTest.class) public void testCallSystemFinalMethod() { String str = PowerMockito.mock(String.class); ClassUnderTest underTest = new ClassUnderTest(); PowerMockito.when(str.isEmpty()).thenReturn(false); Assert.assertFalse(underTest.callSystemFinalMethod(str)); } @Test @PrepareForTest(ClassDependency.class) public void testCallStaticMethod() { ClassUnderTest underTest = new ClassUnderTest(); PowerMockito.mockStatic(ClassDependency.class); PowerMockito.when(ClassDependency.isExist()).thenReturn(true); Assert.assertTrue(underTest.callStaticMethod()); } @Test @PrepareForTest(ClassUnderTest.class) public void testCallSystemStaticMethod() { ClassUnderTest underTest = new ClassUnderTest(); PowerMockito.mockStatic(System.class); PowerMockito.when(System.getProperty("aaa")).thenReturn("bbb"); Assert.assertEquals("bbb", underTest.callSystemStaticMethod("aaa")); } @Test @PrepareForTest(ClassUnderTest.class) public void testCallPrivateMethod() throws Exception { ClassUnderTest underTest = PowerMockito.mock(ClassUnderTest.class); PowerMockito.when(underTest.callPrivateMethod()).thenCallRealMethod(); PowerMockito.when(underTest, "isExist").thenReturn(true); Assert.assertTrue(underTest.callPrivateMethod()); } }
------------------------------------------------------------
下面是我自己的例子
mock静态类
private void prepareForGosServiceFactory()
{
ClassPathXmlApplicationContext ctx=PowerMockito.mock(ClassPathXmlApplicationContext.class);
try {
PowerMockito.whenNew(ClassPathXmlApplicationContext.class).withAnyArguments().thenReturn(ctx);
UpdateSoService updateSoService=PowerMockito.mock(UpdateSoService.class);
PowerMockito.when(ctx.getBean("wrapUpdateOrderHessianCall")).thenReturn(updateSoService);
PowerMockito.mockStatic(GosServiceFactory.class);
PowerMockito.when(GosServiceFactory.getUpdateOrderService()).thenReturn(updateSoService);
PowerMockito.when(updateSoService.updateRemarkIdByOrderId(Mockito.any(RemarkInput.class)));
} catch (Exception e) {
}
}
@Test
public void testDigExperience() {
long peid = 1234L;
int siteId = 1;
String ip = "127.0.0.1";
org.mockito.Mockito.when(productExperienceDao.countExperienceDig(peid, ip)).thenReturn(0L);// mock 返回值
org.mockito.Mockito.doNothing().when(productExperienceDao).updateExperienceUpNum(peid,siteId); // mock void方法
org.mockito.Mockito.doNothing().when(productExperienceDao).updateExperienceDownNum(peid, siteId);
ProductExperienceDig ped = new ProductExperienceDig();
org.mockito.Mockito.when(productExperienceDao.insertPED(ped)).thenReturn(0L);
target.digExperience_pe(peid, ip, 1l, "up");
target.digExperience_pe(peid, ip, 1l, "down");
}
//一个方法重复调用多次,返回值不同的情况,可以这样写
org.mockito.Mockito.when(productExperienceDao.getProductExperienceByIdAndProductIdAndSiteType(Mockito.anyLong(),Mockito.anyLong(),Mockito.anyInt()))
.thenReturn(experienceOld,experienceOld1,experienceOld2,experienceOld3,experienceOld4);