前两天公司的大佬分享了一个测试工具Mokito,当我们在测试代码的时候,基于各种原因,可能有些依赖的接口我们无法调用,这时候我们可以使用mock来模拟我们需要调用的api,然后使其的方法返回我们设定的只,这样能够大大方便我们的测试工作。这几天根据大佬给的博客文章和Mockito的官方文档,自己做了一点小小的总结(其实就是翻译了一遍官方文档),对Mockito有了一个初步的了解,官方文档还有很多比较高级的方法,但是由于时间关系,还没能很好的理解,所以建议有时间的同学可以去看看官方的文档,如果以后有时间我可能也会更新(maybe)然后关于Stubbing的翻译,当时我机译成了存根,也可以翻译成打桩,就是指为mock对象的方法设置返回值的这一过程
附上博客和文档地址
官方文档:
https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#
博客:
https://www.cnblogs.com/Ming8006/p/6297333.html#c1
作者:明-M
以下是个人的总结:
Junit测试与Mock(by Mockito)
静态导入
import static org.mockito.Mockito.*;
1.1 Mock对象
使用mock()返回一个模拟的对象
//简单mock一个对象
List mockList = mock(List.class);
//调用未指定的返回值为非基本类型的方法返回对应类型的“null”值。Integer = 0 ,String = "", Boolean = false;
List mockList = mock(List.class,RETURNS_SMART_NULLS);
//
1.2 为对象的方法规定返回值(存根)
//when(目标方法).thenReturn(返回值);
when(mockList.get(1)).thenReturn("调用get(1)的时候的自定义返回值");
when(mockList.add(1)).thenReturn(true);
//当when后面带有多个thenReturn,会依次执行,然后始终执行最后一个
when(mockList.get(1)).thenReturn(1).thenReturn(2).thenReturn(3);
mockList.get(1); //1
mockList.get(1); //2
mockList.get(1); //3
mockList.get(1); //3
mockList.get(1); //3
1.3 Verify,验证
常见用法如下
//方法被调用1次(不注明times默认1次)
verify(mock).someMethod();
//方法被调用10次
verify(mock, times(10)).someMethod();
//方法被调用至少1次
verify(mock, atLeastOnce()).someMethod();
//方法被调用至少10次
verify(mock, atLeast(10)).someMethod();
//方法没有被调用
verify(mock, never()).someMethod();
1.4 Argument matchers 参数匹配
==注意==:
- 一旦使用参数匹配器,所有参数都要使用参数匹配器:If you are using argument matchers, all arguments have to be provided by matchers.
- 匹配器方法不能在Verify和预设方法之外使用:(如anyObject()、eq()不返回匹配器。在内部,它们在堆栈上记录匹配器并返回一个伪值(通常为null)。这个实现是由于java编译器所施加的静态类型安全。其结果是,不能在已验证/存根方法之外使用anyObject()、eq()方法。
调用函数时,只要传入满足Argument matchers(参数匹配)的参数,对应的结果就会被返回
第一种情况:anyInt,anyString,any......
List mockList = mock(List.class,RETURNS_SMART_NULLS);
when(mockList.get(anyInt())).thenReturn("调用get(int)的时候的自定义返回值");
System.out.println(mockList.get(1)); //"调用get(int)的时候的自定义返回值"
System.out.println(mockList.get(2)); //"调用get(int)的时候的自定义返回值"
System.out.println(mockList.get(3)); //"调用get(int)的时候的自定义返回值"
一些mockito自带的比较器
Methods inherited from class org.mockito.ArgumentMatchers
any, any, anyBoolean, anyByte, anyChar, anyCollection, anyCollectionOf, anyDouble, anyFloat, anyInt, anyIterable, anyIterableOf, anyList, anyListOf, anyLong, anyMap, anyMapOf, anyObject, anySet, anySetOf, anyShort, anyString, anyVararg, argThat, booleanThat, byteThat, charThat, contains, doubleThat, endsWith, eq, eq, eq, eq, eq, eq, eq, eq, eq, floatThat, intThat, isA, isNotNull, isNotNull, isNull, isNull, longThat, matches, matches, notNull, notNull, nullable, refEq, same, shortThat, startsWith
第二种情况:自定义参数匹配器
- 实现ArgumentMatcher<T>接口,重写matches()方法,其中T为我们需要伪造的方法接收的参数类型
这里把匹配规则设置为list的大小为2
class ListOfTwoElements implements ArgumentMatcher<List> {
public boolean matches(List list) {
return list.size() == 2;
}
@Override
public String toString() {
//printed in verification errors
return "[list of 2 elements]";
}
}
- 在设置预设的时候,在预期的参数位置使用argThat( new ListOfTwoElements())
@Test
public void mockArgumentmatchers() {
List mockList = mock(List.class);
when(mockList.addAll(argThat(new ListOfTwoElements()))).thenReturn(true);
System.out.println(mockList.addAll(Arrays.asList("1", "2")));
verify(mockList).addAll(argThat(new ListOfTwoElements()));
}
也可以使用lambda表达式
verify(mock).addAll(argThat(list -> list.size() == 2));
1.5 doThrow抛出异常
预设了抛出异常的方法被调用的时候就会抛出异常
doThrow(new RuntimeException()).when(mockedList).clear();
1.6 inOrder按顺序验证
顺序验证是灵活的-不必验证所有交互
InOrder inOrder = inOrder(mock对象);
InOrder inOrder = inOrder(mockList);
@Test
public void mockInOrder() {
//单一mock对象顺序验证
List mockList = mock(List.class);
//创建InOrder对象
InOrder inorder = inOrder(mockList);
//实际执行顺序
mockList.add(1);
mockList.add(2);
mockList.add(3);
//顺序相同,通过测试,只要验证的顺序与真实执行的一致就行,验证 1 3 也能通过测试
inorder.verify(mockList).add(1);
inorder.verify(mockList).add(2);
inorder.verify(mockList).add(3);
//多个mock对象顺序验证
//创建Inorder对象,传入多个mock对象
InOrder inOrder2 = inOrder(mockListA,mockListB);
mockListA.add("A1");
mockListA.add("A2");
mockListB.add("B1");
mockListB.add("B2");
//验证顺序
inOrder2.verify(mockListA).add("A1");
inOrder2.verify(mockListA).add("A2");
inOrder2.verify(mockListB).add("B1");
inOrder2.verify(mockListB).add("B2");
}
1.7 verifyZeroInteractions验证没有别的mock对象交互
因为mockList2,mockList3没有发生交互所以测试通过
@Test
public void mockVerifyZeroInteractions() {
//创建三个mock对象
List mockList1 = mock(List.class);
List mockList2 = mock(List.class);
List mockList3 = mock(List.class);
//只与mockList1进行交互
mockList1.add(1);
//验证mockList2,MockList3没有发送交互
verifyNoInteractions(mockList2,mockList3);
}
1.8 verifyNoMoreInteractions验证mock对象所有的方法都被我们Verify了
由于参数中的mock对象的交互都被验证了,所以可以通过测试
@Test
public void mockVerifyNoMoreInteractions() {
//创建1个mock对象
List mockList = mock(List.class);
List mockList1 = mock(List.class);
List mockList2 = mock(List.class);
//mockList调用了2次add方法,mockList2调用了一次add方法
mockList.add(1);
mockList.add(2);
mockList2.add(1);
//验证mockList的所有交互
verify(mockList).add(1);
verify(mockList).add(2);
//验证参数中的mock对象是否还有更多没被我们verify的调用
verifyNoMoreInteractions(mockList,mockList1);
}
1.9 @Mock使用注解的方式创建mock对象
MockitoAnnotations.openMocks(this); 和 @RunWith(MockitoJUnitRunner.class),二选一即可,如果不标注,mock对象会为null
//使用注解方式创建mock对象,要在类上标注这个注解Ⅱ
@RunWith(MockitoJUnitRunner.class)
public class MockAnnotationTest {
/**使用注解创建对象*/
@Mock
private MockClass mockClass;
@Mock
private List list;
@Spy
private ArrayList spyList;
@Test
public void testObject() {
//使用注解方式创建mock对象,要在方法内使用这个方法Ⅰ
MockitoAnnotations.openMocks(this);
//使用注解创建的mock对象
mockClass.returnInt();
//verify
verify(mockClass).returnInt();
}
}
1.10 对方法多次调用的存根(类似1.2)
迭代式的设置存根
when(mock.someMethod("some arg"))
.thenThrow(new RuntimeException())
.thenReturn("foo");
//第一次调用 : throws runtime exception:
mock.someMethod("some arg");
//第二次调用 : prints "foo"
System.out.println(mock.someMethod("some arg"));
//之后的每次调用: prints "foo" 换句话说 (最后一个存根赢了).
System.out.println(mock.someMethod("some arg"));
连续存根也可以写成
when(mock.someMethod("some arg"))
.thenReturn("one", "two", "three");
==注意==:重复设置存根的情况,会覆盖存根
//所有对someMethod("some arg")的调用都会返回"two"
when(mock.someMethod("some arg"))
.thenReturn("one")
when(mock.someMethod("some arg"))
.thenReturn("two")
1.11 使用回调设置存根(不推荐)
官方文档不推荐我们使用回调的方法设置存根,如果非要设置,可以参考如下方法
when(mock.someMethod(anyString())).thenAnswer(
new Answer() {
public Object answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
Object mock = invocation.getMock();
return "called with arguments: " + Arrays.toString(args);
}
});
//Following prints "called with arguments: [foo]"
System.out.println(mock.someMethod("foo"));
1.12 对Void方法设置存根
对Void方法设置存根不能用when().thenXXX(),的形式,而是应该使用:
doReturn()
|doThrow()
| doAnswer()
|doNothing()
|doCallRealMethod()
doThrow(new RuntimeException()).when(mockedList).clear();
目前,我们掌握了对有无返回值的方法设置存根的方式:
when(mock.isOk()).thenReturn(true);
when(mock.isOk()).thenThrow(exception);
doThrow(exception).when(mock).someVoidMethod();
1.13 Spy对象,一个mock对象和真实对象的合体(使用doXXX存根)
Object spyObject = spy(Object);可以为我们提供一个真实对象,当我们调用spy对象的方法时,会调用与真实对象一模一样的方法但是我们也可以为他设置存根。(Spy对象也可以使用注解创建)
List list = new LinkedList();
List spy = spy(list);
//为spy的list对象设置存根
when(spy.size()).thenReturn(100);
//spy的list对象的真实方法,这和真实的list对象无异
spy.add("one");
spy.add("two");
//打印我们add的第一个元素 "one"
System.out.println(spy.get(0));
//这个存根被我们重写了,所以会返回100
System.out.println(spy.size());
//我们也可以验证真实方法
verify(spy).add("one");
verify(spy).add("two");
使用spy对象的一些重要的注意事项:
有时候,我们使用when.then来设置存根的时候,可能会出错,例如:
List list = new LinkedList();
List spy = spy(list);
//行,因为mock对象不调用真实对象的副本
List list = mock(ArrayList.class);
when(list.get(0)).thenReturn(10);
System.out.println(list.get(0));
//不行,spy对象调用真实对象的副本,而此时ArrayList还没有初始化,所以会抛出数组越界异常
List spy = spy(new ArrayList());
when(spy.get(0)).thenReturn(10);
System.out.println(spy.get(0));
//行,用doReturn.when就行
List spy = spy(new ArrayList());
doReturn(20).when(spy).get(0);
System.out.println(spy.get(0));
关于spy对象与被spy对象的关系:
Mockito不会委托对传递的实际实例的调用,而是实际创建它的副本。因此,如果你保留了真实的实例并与之交互,就不要期望被监视者知道这些交互作用及其对真实实例状态的影响。推论是,当一个unsubbed方法在spy上被调用而不是在实际实例上调用时,您不会看到对实际实例的任何影响。
关于final方法
注意final方法。Mockito不模拟final方法,所以:当您监视实际对象时+您尝试存根final method=trouble。同样,您也无法验证这些方法。
1.14 Changing default return values of unstubbed invocations (Since 1.7)
您可以为其返回值创建具有指定策略的mock。这是一个相当高级的特性,通常您不需要它来编写像样的测试。但是,它对使用遗留系统很有帮助。
这是默认答案,所以它将被使用只有当你不存根方法调用。
Foo mock = mock(Foo.class, Mockito.RETURNS_SMART_NULLS);
Foo mockTwo = mock(Foo.class, new YourOwnAnswer());
1.15 Answer
Answer接口只有一种方法,因此在JDK8中可以使用lambda表达式去实现他,我们可以通过InvocationOnMock中获取存根的方法的参数,例如:
@Test
public void testAnswer() {
List mockList = mock(ArrayList.class);
//使用lambda表达式实现Answer
doAnswer(returnIs -> false).when(mockList).add(1);
System.out.println(mockList.add(1));
}
或者是在mock对象的时候,传入lambda表达式设置默认返回值
List mockList = mock(ArrayList.class,returnIs -> false);
1.16 reset() 重置mock
reset可以使mock对象忘记所有的存根和交互,不建议使用,因为使用reset()可能意味着我们的测试太多了
List mock = mock(List.class);
when(mock.size()).thenReturn(10);
mock.add(1);
reset(mock);
1.17 timeout() 验证超时(Since 1.8.5)
这使得verify可以更有耐心的等待指定的时间段,来验证方法是否被执行,而不是一检测到方法没执行就立即失败,这对多线程之间的测试可能有用,还没不支持和inOrder一起使用,单位为毫秒
//100ms内,方法执行了即通过
verify(mock, timeout(100)).someMethod();
//100ms内,方法执行了即通过
verify(mock, timeout(100).times(1)).someMethod();
//100ms内,方法执行了2次即通过
verify(mock, timeout(100).times(2)).someMethod();
//100ms内,方法执行了2次即通过
verify(mock, timeout(100).atLeast(2)).someMethod();
1.18 序列化mock对象 (Since 1.8.1)
在创建mock对象时加上参数withSettings().serializable()
List serializableMock = mock(List.class, withSettings().serializable());