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对象,没有设置过的方法行为均返回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");
部分模拟
1、部分模拟的概念
前面提到,如果某个对象被mock,那么它将不再拥有原本的功能,全部的方法都被替换掉了,只会返回设置好的值,如果mock对象的某个方法没有设置,那么就会返回null。当我们想要验证某个对象的行为,同时又需要它一部分功能保持不变时,就要使用部分模拟:
// 使用部分模拟需要用spy方法,它必须是某个已经建立好的对象的封装,不能用class对象为构造参数 List list = new LinkedList(); List mockedList = Mockito.spy(list);
后续我们可以正常调用它的方法,模拟它的部分行为,并验证它的功能:
// 设置method1方法的行为 Mockito.when(mockedList.method1(Mockito.anyInt())).thenReturn("element"); // 调用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
PowerMock可以来配合Mockito使用,模拟静态方法的行为。
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");