Mockito使用总结
Mockito使用总结
写UT时,经常会遇到执行过程中调用的方法返回结果不可控的情况,为了专注在某个确定范围内的开发自测,需要模拟这些方法和类的行为,Mockito提供了很好的解决方案。
使用Mockito可以很方便的设置、校验方法或类的行为,但是前提是首先创建一个mock对象,才能基于Mockito进行操作。
创建一个mock对象
可以简单的调用mock方法来创建一个mock对象:
List mockedList = Mockito.mock(List.class)
或者更简单地,可以直接用@mock:
@Mock
List mockedList;
如果要使用注解,必须打开注解开关:
MockitoAnnotations.initMocks(this);
设置mock对象的行为
我们可以设置mock对象调用某个方法的返回值:
Mockito.when(mockedList.get(0)).thenReturn("val");
或者设置调用方法时抛出某个异常:
Mockito.when(mockedList.get(1)).thenThrow(new RuntimeException());
对于难以mock的方法,直接跳过它:
Mockito.doNothing().when(testObject).handle();
对于一个mock对象,没有设置过的方法行为均返回null:
mockedList.get(999) // 将返回null
在实际使用中常常设置某个方法的返回值为另一个mock对象,在复杂的情况时可以以此来控制整个测试过程。
验证mock对象的行为
1、验证mock对象是否调用过某个方法:
Mockito.verify(mockedList).add("one"); // 是否执行过mockedList.add("one")
注意验证的目标对象必须是mock的,否则会报错。
2、验证方法调用了多少次、是否从未调用过:
Mockito.verify(mockedList, Mockito.times(2)).add("one"); // 是否调用过两次add("one")
Mockito.verify(mockedList, Mockito.never()).add("one"); // 是否从未调用过add("one")
3、验证某个mock对象是否从未使用过:
Mockito.verifyZeroInteractions(mockedList);
4、验证同一个mock对象执行不同方法的先后顺序:
List singleMock = Mockito.mock(List.class);
singleMock.add("first");
singleMock.add("second");
InOrder inOrder = Mockito.inOrder(singleMock);
// 验证调用顺序
inOrder.verify(singleMock).add("first");
inOrder.verify(singleMock).add("second");
5、验证不同mock对象执行不同方法的先后顺序:
List firstMock = Mockito.mock(List.class);
List secondMock = Mockito.mock(List.class);
firstMock.add("first");
secondMock.add("second");
InOrder inOrder = Mockito.inOrder(firstMock,secondMock);
// 验证调用顺序
inOrder.verify(firstMock).add("first");
inOrder.verify(secondMock).add("second");
匹配器
匹配器可以代替一类参数:
1、验证一类行为:
// 验证是否执行过mockedList的get方法,参数为任意int数
Mockito.verify(mockedList).get(Mock.anyInt());
2、设置一类行为:
// 当mockedList调用get方法,参数为任意int数,返回值都是element
Mockito.when(mockedList.get(Mockito.anyInt())).thenReturn("element");
参数捕获验证
参数捕获提供了一种验证的方式:
IUserService userService = Mockito.mock(UserServiceImpl.class);
ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class);
//参数捕获
Mockito.verify(userService).addUser(argument.capture());
//验证
Assert.assertEquals(userName, argument.getValue());
使用Answer接口mock
有时要设置一个对象的复杂行为,这些复杂行为要根据入参的实际情况来区分不同逻辑,此时就需要用Answer接口,如设置除数为0抛异常:
Division testObject = Mockito.mock(Division.class);
//如果是无返回值得 方法可以去掉泛型。把返回值改为Object
Mockito.doAnswer(newAnswer<Integer>() {
@Override
public Integer answer(InvocationOnMock invocation) throws Throwable {
Object[] arguments = invocation.getArguments();
int b = (int) arguments[1];
if(b == 0){
throw new RuntimeException("除数为零");
}
//也可以返回任意需要的值
return (Integer) invocation.callRealMethod();
}
}).when(testObject).divide(Mockito.anyInt(), Mockito.anyInt());
testObject.divide(10, 0);
可以使用doAnswer来mock,也可以直接将Answer对象放入then方法的入参:
Division testObject = Mockito.mock(Division.class);
Mockito.when(testObject.divide(Mockito.anyInt(), Mockito.anyInt())).then(newAnswer<Integer>() {
@Override
public Integer answer(InvocationOnMock invocation) throws Throwable {
Object[] arguments = invocation.getArguments();
int b = (int) arguments[1];
if(b == 0){
throw new RuntimeException("除数为零");
}
//可以返回任意值
return (Integer) invocation.callRealMethod();
}
});
testObject.divide(10, 0);
部分模拟
1、部分模拟的概念
前面提到,如果某个对象被mock,那么它将不再拥有原本的功能,全部的方法都被替换掉了,只会返回设置好的值,如果mock对象的某个方法没有设置,那么就会返回null。当我们想要验证某个对象的行为,同时又需要它一部分功能保持不变时,就要使用部分模拟:
// 使用部分模拟需要用spy方法,它必须是某个已经建立好的对象的封装,不能用class对象为构造参数
List list = new LinkedList();
List mockedList = Mockito.spy(list);
后续我们可以正常调用它的方法,模拟它的部分行为,并验证它的功能:
// 设置method1方法的行为
Mockito.when(mockedList.method1(Mockito.anyInt())).thenReturn("element");
// 上述方式可能报错,也可以这样写
Mockito.doReturn("element").when(mockedList).method1(Mockito.anyInt());
// 调用method2将正常返回原本该返回的值,走正常的方法流程,与mock无关
mockedList.method2();
// 验证对象的行为
Mockito.verify(mockedList).method1(Mock.anyInt());
2、不可模拟情况的替代方案
一旦对象是部分模拟的,在设置行为的过程中就可能发生异常,比如对于一个空集合mockedList,当执行下列语句时就会直接抛出越界异常:
Mockito.when(mockedList.get(0)).thenReturn("foo");
这种情况下应该用这种形式来模拟行为:
Mockito.doReturn("foo").when(mockedList).get(0);
3、使用mock来部分模拟
部分模拟也可以不用spy方法,使用mock一样可以完成相同的功能:
List mockedList = Mockito.mock(List.class);
// 调用某个方法时返回真实值
Mockito.when(mockedList.someMethod()).thenCallRealMethod();
4、注解的使用
和mock一样,spy方法也可以方便的用注解来代替,此时也要注意必须使用初始化后的对象,同时开启注解:
@Spy
List list = new LinkedList();
5、使用部分模拟来完成对局部变量的替换
假设我们要测试这样一段代码:
Request request = new Request();
// 设置request的属性
...
// 调用方法
Response response = serviceInvoker.invoke(SERVICE_NAME, request);
如果我们想模拟invoke方法的行为,很难准确的确定request这个参数,即使创建一个相同的对象,设置其行为:
// 创建request对象并设置其属性
Mockito.when(serviceInvoker.invoke(SERVICE_NAME, request)).thenReturn("foo");
这样也是行不通的,因为在待测试的代码中,request是一个局部变量,它的地址和我们创建出来的对象地址是不同的,也就无法准确的进行模拟。
这种情况解决方案是在待测试代码中将创建request的方法抽取出来:
Request request = getRequest();
// 调用方法
Response response = serviceInvoker.invoke(SERVICE_NAME, request);
然后在UT中,首先mock这个方法getRequest,使其返回值是我们控制好的request对象,然后再进行测试即可,这样就能将我们预期的request对象放入方法中了:
// 创建request对象并设置其属性
Request request = new Request();
...
// 模拟getRequest方法的行为
Mockito.when(instance.getRequest()).thenReturn(request);
// 完成我们一开始想要的行为定义
Mockito.when(serviceInvoker.invoke(SERVICE_NAME, request)).thenReturn("foo");
@InjectMocks
日常开发过程中,另外一个常用的注解是@InjectMocks,用它标注的类会自动装配其中已经被@Mock和@Spy标注的字段:
// ModelDaoImpl类中有一个字段mediator, 这里自动装配其中的mediator字段
@InjectMocks
ModelDaoImpl modelDao;
@Mock
Mediator mediator;
...{
// 这里调用的create是真实的方法,这里面如果有mediator调用某个方法,就可以通过自动装配事先设置它的行为了
Mockito.when(mediator.get(0)).thenReturn("foo");
modelDao.create();
...
}
PowerMock
mock静态方法
PowerMock可以来配合Mockito使用,模拟静态方法的行为。Mockito不能来mock final的类,此时也需要借助PowerMock。
1、使用前的准备
需要在测试类上加注解:
@RunWith(PowerMockRunner.class) // 一个固定的启动类
@PrepareForTest({Factory.class}) // 设置要模拟的静态方法对应的类
2、设置静态方法、或者新建对象的行为:
PowerMockito.mockStatic(Factory.class); // 用spyStatic可以部分模拟,在每次设置行为前都要执行该方法
PowerMockito.when(Factory.getLogger()).thenReturn(logger); // 设置Factory.getLogger()方法返回值
PowerMockito.doThrow(new RunTimeException()).when(Factory.class) // 执行返回值为void的方法时抛出异常
PowerMockito.whenNew(MyClass.class).withNoArguments().thenThrow(new IOEeception()); // 新建对象时抛出异常
3、验证静态方法、或者新建对象的行为:
// 检查getLogger是否调用了2次
PowerMockito.verifyStatic(Mockito.times(2));
Factory.getLogger();
// 检查getLogger是否调用了1次
PowerMockito.verifyStatic(); // default times is once
Factory.getLogger();
// 检查getLogger是否从未被调用
PowerMockito.verifyStatic(Mockito.never());
Factory.getLogger();
// 是否新建过MyClass类的对象
PowerMockito.verifyNew(MyClass.class).withNoArguments();
4、私有方法的模拟与验证:
// classUnderTest调用私有方法methodName,参数为parameter时,返回值为value
PowerMockito.doReturn(value).when(classUnderTest, "methodName", "parameter");
// classUnderTest是否调用两次私有方法methodName,参数为parameter
PowerMockito.verifyPrivate(classUnderTest, Mockito.times(2)).invoke("methodName", "parameter");
5、mock构造方法
运行测试代码时,有时不希望运行构造方法,此时需要mock 构造方法。mock的类需要加入@PrepareForTest:
Bucket bucket = Mockito.mock(Bucket.class);
PowerMockito.whenNew(Bucket.class).withNoArguments().thenReturn(bucket);
用Whitebox代替反射
1、设置私有成员变量
有时在UT中需要设置某个类私有成员变量的值,需要用反射来处理,非常繁琐。powermock给我们提供了简单的API,可以方便的设置私有成员变量的值。
如对象recevier中有私有成员变量名为trainModelDao,同时有另一个对象为trainModelDao,将该对象之间设置到recevier的私有成员变量中:
Whitebox.setInternalState(recevier, "trainModelDao", trainModelDao);
2、直接跳过构造方法创建一个对象:
ExampleWithEvilConstructor tested = Whitebox.newInstance(ExampleWithEvilConstructor.class);
assertNull(tested.getMessage());
几个易错点需要注意
1、有几个@Test标签,就会执行几次@Before @Test @After ,而@BeforeClass和@AfterClass只在一个测试类中执行一次
2、有多个@Test标签时,测试类的构造函数、非static修饰的字段会初始化多次