Mockito使用详解

Mockito使用详解Mockito 是一个针对 Java 的单元测试模拟框架 可以简化单元测试过程中测试上下文对象

大家好,欢迎来到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

(0)
上一篇 2025-10-29 19:15
下一篇 2025-10-29 19:20

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信