Mockito简介
Mockito简介摘自官网,Mockito是一个非常优秀mock框架。他用简单易用的API让单元测试的编写更加简洁优雅。Mockito测试用例可读性高,校验规则清晰。Mockito社区活跃,StackOverflow投票Mockito是最受欢迎的java mock框架。
常用术语
- mock 在测试过程中有许多难以构建的对象如何HttpServletRequest需要依赖Servlet容器,此时就可以使用mock出来一个虚拟的对象,来测试他的方法
- stub 打标 简单的讲就是伪造一个方法,修改原来方法的调用,返回一个伪造的自定义值
- verify 验证 mockito中对操作行为的验证 如mock的对象的add 操作之后是clear操作,verify的顺序也要是add 和 clear
- when then give 等介词是一种BDD(behavior driver delopment)行为驱动开发中的表示条件,断言等状态的介词
Mockito入门
添加maven依赖
<dependencies>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.3.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
基于JunitRunner来运行Mockito
核心利用MockitoJunitRunner来跑Junit的单元测试@RunWith(MockitoJUnitRunner.class)
// 使用Junit的MockitoRunner开启Mockito测试
@RunWith(MockitoJUnitRunner.class)
public class SimpleUseMockito {
/**
* 基于Mockito的校验操作行为
*/
@Test
public void testSimpleVerifyBehavior(){
// 使用mockito的静态方法生成一个mock 对象
ArrayList<Integer> list = mock(ArrayList.class);
System.out.println(list.getClass());
// mock出来的对象的一些行为
list.add(0);
list.add(1);
list.clear();
// 使用mockito 的verify 方法来校验操作步骤
verify(list).add(0);
verify(list).add(1);
verify(list).clear();
}
}
基于Annation来运行Mockito
使用MockitoAnnotations.init(testClass) 来初始化一个需要mock的类
@Mock 注解来表示该对象是mock出来的
public class UseMockitoByAnnotation {
@Mock
List<Integer> list;
@Before
public void init() {
MockitoAnnotations.initMocks(this);
}
@Test
public void test1() {
doReturn(1).when(list).get(0);
assertThat(list.get(0), equalTo(1));
}
@After
public void destroy() {
}
}
基于Junit Rule 运行Mockito
MockitoJunit.rule()创建一个Junit的Rule 其他使用方式与注解和Runner类似
public class UseMockitoByRule {
@Rule
public MockitoRule mockitorule = MockitoJUnit.rule();
@Mock
List<Integer> list;
@Test
public void test1() {
doReturn(1).when(list).get(0);
assertThat(list.get(0), equalTo(1));
}
}
verify 行为验证
/**
* 基于Mockito的校验操作行为
*/
@Test
public void testSimpleVerifyBehavior(){
// 使用mockito的静态方法生成一个mock 对象
ArrayList<Integer> list = mock(ArrayList.class);
System.out.println(list.getClass());
// mock出来的对象的一些行为
list.add(0);
list.add(1);
list.clear();
// 使用mockito 的verify 方法来校验操作步骤 这个对顺序没有要求
verify(list).add(0);
verify(list).add(1);
verify(list).clear();
}
Verify 验证带次数
@Test
public void testVerifyByTime() {
ArrayList<Integer> list = mock(ArrayList.class);
list.add(1);
list.add(2);
list.add(2);
list.add(3);
list.add(3);
list.add(3);
// time(int) 调用次数验证
verify(list, times(1)).add(1);
verify(list, times(2)).add(2);
verify(list, times(3)).add(3);
// never() 一次都没调用
verify(list, never()).add(4);
// atLeastOnce 至少一次 atMostOnce 最多一次
// atLeast(int) 至少X次 atMost(int) 最多X次
verify(list, atLeastOnce()).add(1);
verify(list, atMostOnce()).add(1);
verify(list, atLeastOnce()).add(2);
verify(list, atLeast(2)).add(2);
verify(list, atLeast(3)).add(3);
verify(list, atMost(5)).add(3);
}
Verify 验证带顺序
public void testVerifyInOrder() {
// 单个mock对象
ArrayList<Integer> single = mock(ArrayList.class);
single.add(1);
single.add(2);
InOrder inOrder = inOrder(single);
// 被抛出VerificationInOrderFailure 的Error
// inOrder.verify(single).add(2);
// inOrder.verify(single).add(1);
inOrder.verify(single).add(1);
inOrder.verify(single).add(2);
// 多个mock对象
ArrayList<Integer> mul1 = mock(ArrayList.class);
ArrayList<Integer> mul2 = mock(ArrayList.class);
mul1.add(1);
mul2.add(2);
InOrder inOrderMul = inOrder(mul1,mul2);
// 必须是mul1调用add(1) 在mul2调用add(2)之前
inOrderMul.verify(mul1).add(1);
inOrderMul.verify(mul2).add(2);
}
VerifyNoInterations 验证类没有被调用过
@Test
public void testVerifyNeverInvoke(){
// 多个mock对象
ArrayList<Integer> mul1 = mock(ArrayList.class);
ArrayList<Integer> mul2 = mock(ArrayList.class);
mul1.add(1);
verify(mul1).add(1);
// 验证没有调用过
verifyNoInteractions(mul2);
}
Stub基础使用
// 最简单的stub
@Test
public void simpleStubbing() {
// mock 一个 list对象
ArrayList<Integer> list = mock(ArrayList.class);
// 使用stubbing 伪造list.get(0)的结果返回0
when(list.get(0)).thenReturn(0);
// 调用list.get(0) 然后去断言 通过 其实本身这个list是没有数据的 通过mockito Stub了一个伪造的数据
assertThat(list.get(0),equalTo(0));
}
// 带有异常的stubbing
@Test
public void simpleExceptionStubbing(){
ArrayList<Integer> list = mock(ArrayList.class);
// 使用thenThrow 抛出一个异常
when(list.get(0)).thenThrow(new RuntimeException());
try {
list.get(0);
fail();
} catch (Exception e) {
assertThat(e,instanceOf(RuntimeException.class));
}
}
// doXXX的Stub
// 用于stubbing 没有返回值的可以使用doReturn()|doThrow()| doAnswer()|doNothing()|doCallRealMethod() 这几个方法 后面接when(需要Stub对象).xxx方法()
@Test
public void useDoXXStubbing() {
// 使用doXX可以mock没有返回值的请求
ArrayList<Integer> list = mock(ArrayList.class);
// 使用doReturn 实现与 thenReturn 类似效果
doReturn(1).when(list).get(0);
assertThat(list.get(0), equalTo(1));
// 使用doThrow 实现 与 thenThrow类似效果
doThrow(new UnsupportedOperationException()).when(list).clear();
try {
list.clear();
fail();
} catch (Exception e) {
assertThat(e, instanceOf(UnsupportedOperationException.class));
}
}
/**
* 使用Answer 高度定制Stub
*/
@Test
public void testAnswer(){
ArrayList<Integer> list = mock(ArrayList.class);
doAnswer(invocation -> {
// invocation 中可以获取方法的参数 方法 mock对象 或者调用真实的方法
Object arg1 = invocation.getArgument(0);
System.out.println(arg1);
return 1;
}).when(list).get(0);
assertThat(list.get(0), equalTo(1));
}
连续Stubbing
@Test
public void testStubbingConsecutive(){
ArrayList<Integer> list = mock(ArrayList.class);
// 调用的调用thenXXX进行Stubbing
when(list.get(0)).thenReturn(100).thenThrow(new UnsupportedOperationException());
assertThat(list.get(0),equalTo(100));
try {
list.get(0);
} catch (Exception e) {
assertThat(e,instanceOf(UnsupportedOperationException.class));
}
}
mockito调用真实方法
// 调用真实的方法
public class TestService {
private final InnerTestService innerTestService;
public TestService(InnerTestService innerTestService) {
this.innerTestService = innerTestService;
}
public Integer test1() {
System.out.println("really do test1");
return 1;
}
public void test2(String val) {
System.out.println("really do test2 " + val);
}
}
public class InnerTestService {
}
@Test
public void testRealCallMethod(){
TestService testService = mock(TestService.class);
// 调用的mockito代理出来的mock方法不会调用真实的方法 不会打印 really do test1
when(testService.test1()).thenReturn(1);
assertThat(testService.test1(), equalTo(1));
// 申明 testService.test或调用真的testService 方法 控制台会打印 really do test1
when(testService.test1()).thenCallRealMethod();
assertThat(testService.test1(), equalTo(1));
}
mockito deep mock
// deep mock 深度mock
@Test
public void testDeepMock() {
// deep mock 表示深度mock 对象里面包含的其他对象
TestService noDeepMock = mock(TestService.class);
try {
// 这里调用被抛出NPE
noDeepMock.testInnerTest().test();
} catch (Exception e) {
// 断言exception
assertThat(e, instanceOf(NullPointerException.class));
}
// 使用 deep stub方法 mockito会将 对象中关联依赖的对象一并mock 不会出现 NoPointException
TestService testService = mock(TestService.class, Answers.RETURNS_DEEP_STUBS);
when(testService.testInnerTest().test()).thenReturn(1);
assertThat(testService.testInnerTest().test(), equalTo(1));
}
mockito spy的使用
可以创建一个真实对象的spy,当使用spy对象的时候会真正调用真实对象的方法(除非改方法被stub)
/**
* 可以创建一个真实对象的spy,当使用spy对象的时候会真正调用真实对象的方法(除非改方法被stub)
* 跟mock不同的是,利用Spy可以做到部分的mock,只在需要mock的部分做stubbing即可完成mock
*/
@Test
public void testSpy() {
// 1. 创建真实的对象
List<Integer> realList = new ArrayList<>();
// 调用spy传入真实对象返回改对象的SPY
List<Integer> spy = spy(realList);
// 真实调用方法
spy.add(1);
spy.add(2);
spy.add(3);
assertThat(spy.size(), equalTo(3));
// 如果需要mock处理 做一个特殊的stubbing 即可
when(spy.get(0)).thenReturn(100);
assertThat(spy.get(0), equalTo(100));
}
spy真实对象时候的坑
在spy调用真实对象的时候可能会出现异常,此时用doXXX方法来解决
@Test
public void testSpyGotcha() {
// 1. 创建真实的对象
List<Integer> realList = new ArrayList<>();
// 调用spy传入真实对象返回改对象的SPY
List<Integer> spy = spy(realList);
try {
// 因为会调用真实对象 所有spy.get(0) 会报索引越界
when(spy.get(0)).thenReturn(1);
} catch (Exception e) {
assertThat(e, instanceOf(IndexOutOfBoundsException.class));
}
// 使用doReturn 来解决这个问题
doReturn(1).when(spy).get(0);
assertThat(spy.get(0),equalTo(1));
}
修改没有被stub方法默认的返回值规则
Foo mock = mock(Foo.class, Mockito.RETURNS_SMART_NULLS);
Foo mockTwo = mock(Foo.class, new YourOwnAnswer());
mockito 参数捕获 ArgumentCaptor
mockito在做测试时候有时候不仅需要对测试的返回值进行断言,有时也会对调用的方法参数传入的具体参数值进行断言,此时可以使用ArgumentCaptor来捕获参数值
需要特别注意的是ArgumentCaptor只能用在verfication的时候不能使用在stub中,在stubbing中使用ArgumentCaptor会减少测试的可靠性,因为ArgumentCaptor是在assert块之外创建的,也会因为stubbing的方法没有调用导致没有参数可以捕获导致不好定位错误。
/**
* argument.capture() 捕获方法参数
* argument.getValue() 获取方法参数值,如果方法进行了多次调用,它将返回最后一个参数值
* argument.getAllValues() 方法进行多次调用后,返回多个参数值
*/
@Test
public void testArgumentCapturing() {
ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class);
List<String> list1 = mock(List.class);
List<String> list2 = mock(List.class);
list1.add("jack");
list2.add("tom");
list2.add("tomcat");
// 在verify中使用 使用ArgumentCaptor 捕获传入的参数
verify(list1, times(1)).add(argumentCaptor.capture());
assertThat("jack", equalTo(argumentCaptor.getValue()));
verify(list2, times(2)).add(argumentCaptor.capture());
assertArrayEquals(new String[]{"jack", "tom", "tomcat"}, argumentCaptor.getAllValues().toArray());
}
Mockito 对BDD的别名支持
BDD ( Behavior Driver Delopment )基于行为驱动开发,具体参考wiki或者其他关于BDD的相关资料
@Test
public void testSupport4BDD(){
List<Integer> mock = mock(List.class);
// give
given(mock.get(0)).willReturn(100);
// when
Integer result = mock.get(0);
// then
assertThat(result,equalTo(100));
}
Mockito对Serializable的支持
mock可以被序列化,使用mockito的序列化支持可以在一些依赖需要序列化的时候进行mock
List serializableMock = mock(List.class, withSettings().serializable());
Mockito对final Class finalMethods enum的mock支持 (2.1.0之后)
使用方法在classpath下创建一个mockito-extensions目录创建一个org.mockito.plugins.MockMaker文件内容为mock-maker-inline
即可mock final类型
Mockito中参数统配
使用过程中需要注意参数统配的顺序,范围越大的统配放在后面会覆盖范围具体的统配条件
@Test
public void testArgument() {
MockitoWildCardArgumentClass mock = mock(MockitoWildCardArgumentClass.class);
// stub anyString传入任意String anyInt任意int anyCollection任意collection的子类 any()任意一个class值保证编译不报错
when(mock.function1(anyString(),anyInt(),anyCollection(),any())).thenReturn("test arg");
assertThat(mock.function1("1",1, Collections.emptyList(),"s"),equalTo("test arg"));
assertThat(mock.function1("2",2, Collections.emptySet(),"s"),equalTo("test arg"));
}
/**
* 部分统配 部分特殊定制值eq()方法
*/
@Test
public void testWildCardArgument() {
MockitoWildCardArgumentClass mock = mock(MockitoWildCardArgumentClass.class);
// stub 中其中某一些参数需要指定 则用到eq 如果不使用eq直接使用会报 InvalidUseOfMatchersException
when(mock.function1(anyString(),eq(1),anyCollection(),any())).thenReturn("eq 1");
when(mock.function1(anyString(),eq(2),anyCollection(),any())).thenReturn("eq 2");
assertThat(mock.function1("1",1, Collections.emptyList(),"s"),equalTo("eq 1"));
assertThat(mock.function1("2",2, Collections.emptySet(),"s"),equalTo("eq 2"));
}
/**
* 统配的使用顺序
*/
@Test
public void testWildCardArgumentOrderErorr() {
MockitoWildCardArgumentClass mock = mock(MockitoWildCardArgumentClass.class);
// 在使用统配参数的时候需要注意使用的顺序 必须把通用的放在之前 如果把通用的放在最后会覆盖之前特别定制的参数
when(mock.function1(anyString(),eq(1),anyCollection(),any())).thenReturn("eq 1");
when(mock.function1(anyString(),eq(2),anyCollection(),any())).thenReturn("eq 2");
// 统配在最后 会覆盖之前特殊处理的stub
when(mock.function1(anyString(),anyInt(),anyCollection(),any())).thenReturn("eq anyInt");
assertThat(mock.function1("1",1, Collections.emptyList(),"s"),equalTo("eq anyInt"));
assertThat(mock.function1("2",2, Collections.emptySet(),"s"),equalTo("eq anyInt"));
}
/**
* 统配的使用顺序
*/
@Test
public void testWildCardArgumentOrderSuccess() {
MockitoWildCardArgumentClass mock = mock(MockitoWildCardArgumentClass.class);
// 在使用统配参数的时候需要注意使用的顺序 必须把通用的放在之前 如果把通用的放在最后会覆盖之前特别定制的参数
// 统配的在之前 正常匹配特殊定制的
when(mock.function1(anyString(),anyInt(),anyCollection(),any())).thenReturn("eq anyInt");
when(mock.function1(anyString(),eq(1),anyCollection(),any())).thenReturn("eq 1");
when(mock.function1(anyString(),eq(2),anyCollection(),any())).thenReturn("eq 2");
assertThat(mock.function1("1",1, Collections.emptyList(),"s"),equalTo("eq 1"));
assertThat(mock.function1("2",2, Collections.emptySet(),"s"),equalTo("eq 2"));
}