大家好,欢迎来到IT知识分享网。
一、介绍
Mockito 是一个针对 Java 的单元测试模拟框架,可以简化单元测试过程中测试上下文对象。
它可以做如下事情:
- 模拟方法的返回值、模拟抛出异常
- 验证方法被调用次数、验证方法参数类型
- 捕获方法参数值
- 为真实对象创建一个监控(spy)对象
注意:
- 不能 Mock 静态方法
- 不能 Mock private 方法
- 不能 Mock final class
概念介绍
二、使用介绍
spring-boot-starter-test已经集成了mockito,所以SpringBoot项目无法另外引入依赖。
如果非SpringBoot项目可以类似如下引入:
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.8.2</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>4.5.1</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> <version>4.5.1</version> <scope>compile</scope> </dependency>
常用方法
| 方法名 | 描述 |
|---|---|
| Mockito.mock(classToMock) | 模拟对象 |
| Mockito.mock(classToMock, defaultAnswer) | 使用默认Answer模拟对象 |
| Mockito.verify(mock) | 验证行为是否发生 |
| Mockito.when(methodCall).thenReturn(value) | 设置方法预期返回值 |
Mockito.when(methodCall).thenReturn(value1).thenReturn(value2) //等价于Mockito.when(methodCall).thenReturn(value1, value2) |
触发时第一次返回value1,第n次都返回value2 |
| Mockito.when(methodCall).thenAnswer(answer)) | 预期回调接口生成期望值 |
| Mockito.doThrow(toBeThrown).when(mock).[method] | 模拟抛出异常。 |
| Mockito.doReturn(toBeReturned).when(mock).[method] | 设置方法预期返回值(直接执行不判断) |
| Mockito.doAnswer(answer).when(methodCall).[method] | 预期回调接口生成期望值(直接执行不判断) |
| Mockito.doNothing().when(mock).[method] | 不做任何返回 |
Mockito.doCallRealMethod().when(mock).[method] //等价于Mockito.when(mock.[method] ).thenCallRealMethod(); |
调用真实的方法 |
| Mockito.spy(Object) | 用spy监控真实对象,设置真实对象行为 |
| Mockito.inOrder(Object… mocks) | 创建顺序验证器 |
| Mockito.reset(mock) | 重置mock |
1、简单使用
一旦mock对象被创建了,mock对象会记住所有的交互,然后你就可以选择性的验证你感兴趣的交互,验证不通过则抛出异常。
mock函数默认返回的是null、一个空的集合或者一个被对象类型包装的内置类型(例如0、false对应的对象类型为Integer、Boolean)。
一旦测试桩函数被调用,该函数将会一直返回固定的值。
@Test @DisplayName("mock简单使用") public void test1() { // 创建一个Mock对象 List mockedList = Mockito.mock(List.class); //存根方法,调用get(0)时,返回first Mockito.when(mockedList.get(0)).thenReturn("first"); // 以下代码实现相同的效果 // Mockito.doReturn("first").when(mockedList).get(0); //返回first System.out.println(mockedList.get(0)); //没有存根,则会返回null System.out.println(mockedList.get(999)); // 验证方法被调用次数,指定参数类型匹配器 Mockito.verify(mockedList, times(2)).get(anyInt()); //存根方法,调用get(1)时,直接抛出异常 Mockito.when(mockedList.get(1)).thenThrow(new RuntimeException()); // 以下代码实现相同的效果 // Mockito.doThrow(new RuntimeException()).when(mockedList).get(1); //抛出异常 System.out.println(mockedList.get(1)); }
2、参数匹配器
@Test public void argMatch(){ // 创建一个Mock对象 List<String> mockedList = Mockito.mock(List.class); //使用内置的 anyInt() 参数匹配器 when(mockedList.get(anyInt())).thenReturn("element"); //使用自定义匹配器 (假设 isValid() 返回你自己的匹配器实现): when(mockedList.contains(argThat(isValid()))).thenReturn(true); //打印 "element" System.out.println(mockedList.get(999)); //验证方法调用时,使用参数匹配器 verify(mockedList).get(anyInt()); mockedList.add("12345"); //使用 Java 8 Lambdas 实现参数匹配器 verify(mockedList).add(argThat(someString -> someString.length() > 5)); }
如果你使用了参数匹配器,方法中的所有参数都必须是匹配器。
verify(mock).someMethod(anyInt(), anyString(), eq("third argument")); //上面是正确的 - eq() 也是一个参数匹配器 verify(mock).someMethod(anyInt(), anyString(), "third argument"); //上面代码是错误的 - 因为第三个参数不是参数匹配器
参数匹配器列表
org.mockito.ArgumentMatchers中定义了所有的内置匹配器
| 函数名 | 说明 |
|---|---|
| any() | 任意类型 |
| any(Class<T> type) | 任意指定的Class类型,除了null |
| isA(Class<T> type) | 指定类型的实现对象 |
| anyInt() | 任何int或者non-null Integer |
| anyXxx() | 其他类似还有(Boolean、Byte、Char、Int、Long、Float、Double、Short、String、List、Set、Map、Collection、Iterable)同样必须非空 |
| eq(value) | 等于给定的值 |
| same(value) | 和给定的值是同一个对象 |
| isNull() | null值 |
| notNull() | 非null |
| nullable(Class<T> clazz) | null 或者给定的类型 |
| contains(String substring) | 包含指定的字符串 |
| matches(String regex) | 匹配正则表达式 |
| endsWith(String suffix) | 以xx结尾 |
| startsWith(String prefix) | 以xx开头 |
| argThat(ArgumentMatcher<T> matcher) | 自定义匹配器 |
3、验证精确调用次数
verify()默认验证方法被调用1次,可以传入times()方法,匹配精确的次数,或者其他类似方法
- times(n),匹配n次
- never(),没被调用,等于times(0)
- atMostOnce(),最多1次
- atLeastOnce(),最少1次
- atLeast(n),最少n次
- atMost(n),最多n次
//using mock mockedList.add("once"); mockedList.add("twice"); mockedList.add("twice"); mockedList.add("three times"); mockedList.add("three times"); mockedList.add("three times"); //following two verifications work exactly the same - times(1) is used by default verify(mockedList).add("once"); verify(mockedList, times(1)).add("once"); //exact number of invocations verification verify(mockedList, times(2)).add("twice"); verify(mockedList, times(3)).add("three times"); //verification using never(). never() is an alias to times(0) verify(mockedList, never()).add("never happened"); //verification using atLeast()/atMost() verify(mockedList, atMostOnce()).add("once"); verify(mockedList, atLeastOnce()).add("three times"); verify(mockedList, atLeast(2)).add("three times"); verify(mockedList, atMost(5)).add("three times");
4、验证执行顺序
可以通过Mockito.inOrder(Object... mocks)创建顺序验证器
// A. 单个mock对象调用顺序验证 List singleMock = mock(List.class); //using a single mock singleMock.add("was added first"); singleMock.add("was added second"); //创建顺序验证器,使用单个mock InOrder inOrder = inOrder(singleMock); //以下代码验证先调用 "was added first", 然后调用 "was added second" inOrder.verify(singleMock).add("was added first"); inOrder.verify(singleMock).add("was added second"); // B. 组合 mocks 调用顺序验证 List firstMock = mock(List.class); List secondMock = mock(List.class); //using mocks firstMock.add("was called first"); secondMock.add("was called second"); //创建顺序验证器,使用多个mock InOrder inOrder = inOrder(firstMock, secondMock); //以下代码验证 firstMock 在 secondMock 之前调用 inOrder.verify(firstMock).add("was called first"); inOrder.verify(secondMock).add("was called second");
5、监控真实对象
可以为真实对象创建一个监控(spy)对象。当你使用这个spy对象时真实的对象也会也调用,除非它的函数被stub了。
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");
List list = new LinkedList(); List spy = spy(list); //注意: 真实对象方法会被调用, 所以spy.get(0) 会抛出异常 IndexOutOfBoundsException (list 目前还是空的) when(spy.get(0)).thenReturn("foo"); //可以使用 doReturn() 来存根 doReturn("foo").when(spy).get(0);
6、自定义验证失败信息
@Test public void test() { final ArrayList arrayList = mock(ArrayList.class); arrayList.add("one"); arrayList.add("two"); verify(arrayList, description("size()没有调用")).size(); // org.mockito.exceptions.base.MockitoAssertionError: size()没有调用 verify(arrayList, timeout(200).times(3).description("验证失败")).add(anyString()); //org.mockito.exceptions.base.MockitoAssertionError: 验证失败 }
7、参数捕捉器
ArgumentCaptor argument = ArgumentCaptor.forClass(Class clazz) 创建指定类型的参数捕获器
argument.capture() 捕获方法参数
argument.getValue() 获取方法参数值,如果方法进行了多次调用,它将返回最后一个参数值
argument.getAllValues() 方法进行多次调用后,返回多个参数值
@Test @DisplayName("参数捕捉器") public void argumentCaptor() { List mock = mock(List.class); List mock1 = mock(List.class); mock.add("John"); mock1.add("Brian"); mock1.add("Jim"); // 获取方法参数 ArgumentCaptor argument = ArgumentCaptor.forClass(String.class); verify(mock).add(argument.capture()); System.out.println(argument.getValue()); //John // 多次调用获取最后一次 ArgumentCaptor argument1 = ArgumentCaptor.forClass(String.class); verify(mock1, times(2)).add(argument1.capture()); System.out.println(argument1.getValue()); //Jim // 获取所有调用参数 System.out.println(argument1.getAllValues()); //[Brian, Jim] }
8、doAnswer回调函数
@Test @DisplayName("设置方法回调函数") public void mockListAnswer() { List mockedList = mock(List.class); // Mockito.when(mockedList.get(Mockito.anyInt())).thenAnswer(invocationOnMock -> { // System.out.println("哈哈哈,被我逮到了吧"); // Object[] arguments = invocationOnMock.getArguments(); // System.out.println("参数为:" + Arrays.toString(arguments)); // Method method = invocationOnMock.getMethod(); // System.out.println("方法名为:" + method.getName()); // return "结果由我决定"; // }); Mockito.doAnswer(invocationOnMock -> { System.out.println("哈哈哈,被我逮到了吧"); Object[] arguments = invocationOnMock.getArguments(); System.out.println("参数为:" + Arrays.toString(arguments)); Method method = invocationOnMock.getMethod(); System.out.println("方法名为:" + method.getName()); return "结果由我决定"; }).when(mockedList).get(anyInt()); System.out.println(mockedList.get(0)); }
9、设置mock默认行为
// 创建mock对象、使用默认返回
final ArrayList mockList = mock(ArrayList.class);
System.out.println(mockList.get(0)); //null
// 这个实现首先尝试全局配置,如果没有全局配置就会使用默认的回答,它返回0,空集合,null,等等。
// 参考返回配置:ReturnsEmptyValues
mock(ArrayList.class, Answers.RETURNS_DEFAULTS);
// ReturnsSmartNulls首先尝试返回普通值(0,空集合,空字符串,等等)然后它试图返回SmartNull。
// 如果最终返回对象,那么会简单返回null。一般用在处理遗留代码。
// 参考返回配置:ReturnsMoreEmptyValues
mock(ArrayList.class, Answers.RETURNS_SMART_NULLS);
// 未stub的方法,会调用真实方法。
// 注1:存根部分模拟使用时(mock.getSomething ()) .thenReturn (fakeValue)语法将调用的方法。对于部分模拟推荐使用doReturn语法。
// 注2:如果模拟是序列化反序列化,那么这个Answer将无法理解泛型的元数据。
mock(ArrayList.class, Answers.CALLS_REAL_METHODS);
// 深度stub,用于嵌套对象的mock。参考:https://www.cnblogs.com/Ming8006/p/6297333.html
mock(ArrayList.class, Answers.RETURNS_DEEP_STUBS);
// ReturnsMocks首先尝试返回普通值(0,空集合,空字符串,等等)然后它试图返回mock。
// 如果返回类型不能mocked(例如是final)然后返回null。
mock(ArrayList.class, Answers.RETURNS_MOCKS);
// mock对象的方法调用后,可以返回自己(类似builder模式)
mock(ArrayList.class, Answers.RETURNS_SELF);
// 自定义返回
final Answer<String> answer = new Answer<String>() {
@Override
public String answer(InvocationOnMock invocation) throws Throwable {
return "test_answer";
}
};
final ArrayList mockList1 = mock(ArrayList.class, answer);
System.out.println(mockList1.get(0)); //test_answer
10、配合注解使用@Mock、@Spy
注解必须结合扩展@ExtendWith(MockitoExtension.class)一起使用,否则无法自动创建对象
@Mock创建一个Mock对象@Spy创建一个Spy对象(不能是接口或抽象类)@InjectMocks被测试类标注为@InjectMocks时(不能是接口或抽象类),会自动实例化,并且把@Mock或者@Spy标注过的依赖注入进去
@ExtendWith(MockitoExtension.class) @DisplayName("mock单元测试") public class Mockito2Test { // 创建一个Mock对象 @Mock private List mockedList; // 创建一个Spy对象,不能是接口或抽象类 @Spy private ArrayList spyList; // RegisterServiceImpl 依赖 UserDao @InjectMocks private RegisterServiceImpl registerService; // 创建一个Mock对象 @Mock private UserDao userDao; @Test @DisplayName("mock简单使用") public void test1() { //调用get(0)时,返回first Mockito.when(mockedList.get(0)).thenReturn("first"); //返回first System.out.println(mockedList.get(0)); //没有存根,则会返回null System.out.println(mockedList.get(999)); // 验证方法被调用次数,指定参数类型匹配器 Mockito.verify(mockedList, times(2)).get(anyInt()); } @Test @DisplayName("spy函数") public void spyTest() { spyList.add("01"); spyList.add("02"); doReturn("first").when(spyList).get(anyInt()); //返回first System.out.println(spyList.get(0)); reset(spyList); System.out.println(spyList.get(0)); //验证是否调用过get函数。这里的anyInt()就是一个参数匹配器。 verify(spyList).get(anyInt()); } @Test public void register() { UserEntity entity = new UserEntity(); entity.setName("test"); doReturn(entity).when(userDao).save(any(UserEntity.class)); // 调用注册方法,实际调用的userDao.save() UserEntity register = registerService.register("123"); // 返回entity System.out.println("register = " + register); // 存根save方法,预期返回上面的对象entity // 存根findByName方法,预期返回上面的对象entity doReturn(entity).when(userDao).findByName("123"); // 调用getUser方法,返回上面的entity对象,调用userDao.findByName() UserEntity user = registerService.getUser("123"); // 返回entity System.out.println("user = " + user); // 判断userDao的save方法是否被调用了一次 verify(userDao).save(isA(UserEntity.class)); // 判断userDao的findByName方法是否被调用了一次 verify(userDao).findByName(anyString()); } }
三、集合SpringBoot使用
SpringBoot集成了Mockito,可以实现对Spring容器中的Bean对象进行mock。
直接引入以下依赖即可:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
SpringBoot增加了注解@MockBean和@SpyBean,结合@SpringBootTest使用可以自动将Spring容器中的Bean对象的依赖Bean替换为mock或spy对象
怎么理解这句话呢,以下代码示例:
1)RegisterServiceImpl依赖UserDao
public interface IRegisterService { UserEntity register(String name); UserEntity getUser(String name); } @Service public class RegisterServiceImpl implements IRegisterService { @Resource private UserDao userDao; @Override @Transactional public UserEntity register(String name) { UserEntity entity = new UserEntity(); entity.setName(name); return userDao.save(entity); } @Override public UserEntity getUser(String name) { return userDao.findByName(name); } }
2)编写单元测试RegisterServiceTest
@SpringBootTest(classes = MockitoApp.class) @DisplayName("用户注册单元测试") public class RegisterServiceTest { @Autowired private IRegisterService registerService; @MockBean private UserDao userDao; @Test public void register() { UserEntity entity = new UserEntity(); entity.setName("test"); doReturn(entity).when(userDao).save(any(UserEntity.class)); // 调用注册方法,实际调用的userDao.save() UserEntity register = registerService.register("123"); // 返回entity System.out.println("register = " + register); // 存根save方法,预期返回上面的对象entity // 存根findByName方法,预期返回上面的对象entity doReturn(entity).when(userDao).findByName("123"); // 调用getUser方法,返回上面的entity对象,调用userDao.findByName() UserEntity user = registerService.getUser("123"); // 返回entity System.out.println("user = " + user); // 判断userDao的save方法是否被调用了一次 verify(userDao).save(isA(UserEntity.class)); // 判断userDao的findByName方法是否被调用了一次 verify(userDao).findByName(anyString()); } }
参考
- https://site.mockito.org/
- https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html
- https://juejin.cn/post/
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/120432.html