Mockito 是一个针对 Java 的单元测试模拟框架,可以简化单元测试过程中测试上下文对象。
它可以做如下事情:
注意:
1)Mockito:简单轻量级的做mocking测试的框架;
2)mock对象:在调试期间用来作为真实对象的替代品;
3)mock测试:在测试过程中,对那些不容易构建的对象用一个虚拟对象来代替测试的方法就叫mock测试;
4)stub:存根,就是为mock对象的方法指定返回值(可抛出异常);
5)verify:行为验证,验证指定方法调用情况(是否被调用,调用次数等);
spring-boot-starter-test
已经集成了mockito
,所以SpringBoot项目无法另外引入依赖。
如果非SpringBoot项目可以类似如下引入:
<dependency>
<groupId>org.junit.jupitergroupId>
<artifactId>junit-jupiterartifactId>
<version>5.8.2version>
<scope>compilescope>
dependency>
<dependency>
<groupId>org.mockitogroupId>
<artifactId>mockito-coreartifactId>
<version>4.5.1version>
<scope>compilescope>
dependency>
<dependency>
<groupId>org.mockitogroupId>
<artifactId>mockito-junit-jupiterartifactId>
<version>4.5.1version>
<scope>compilescope>
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 |
一旦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));
}
@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 |
任意指定的Class类型,除了null |
isA(Class |
指定类型的实现对象 |
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 |
null 或者给定的类型 |
contains(String substring) | 包含指定的字符串 |
matches(String regex) | 匹配正则表达式 |
endsWith(String suffix) | 以xx结尾 |
startsWith(String prefix) | 以xx开头 |
argThat(ArgumentMatcher |
自定义匹配器 |
verify()默认验证方法被调用1次,可以传入times()方法,匹配精确的次数,或者其他类似方法
//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");
可以通过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");
可以为真实对象创建一个监控(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");
重要提示!
有时,spy对象不能使用when(Object)来stub方法,请考虑使用doReturn|Answer|Throw()
系列方法来stub方法,看下面例子:
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);
@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: 验证失败
}
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]
}
@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));
}
// 创建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
注解必须结合扩展@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集成了Mockito,可以实现对Spring容器中的Bean对象进行mock。
直接引入以下依赖即可:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
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());
}
}