官网
本文是由博主呕心沥血总结,有极大实用性的生产工具总结,阅读前前收藏。
mockito:一个用来mock测试的工具,能提高提高自测效率。
mock 测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。这个虚拟的对象就是mock对象。mock对象就是真实对象在调试期间的代替品
我们在测试类 A 时,类 A 需要调用类 B 和类 C,而类 B 和类 C 又需要调用其他类如 D、E、F 等,假如类 D、E、F 构造很耗时又或者调用很耗时的话是非常不便于测试的(比如是 DAO 类,每次访问数据库都很耗时)。所以我们引入 Mock 对象
如上图,我们将类 B 和类 C 替换成 Mock 对象,在调用类 B 和类 C 的方法时,用 Mock 对象的方法来替换(当然我们要自己设定参数和期望结果)而不用实际去调用其他类。这样测试效率会高很多。
一句话为什么要 Mock:因为我们实际编写程序都不会是一个简单类,而是有着复杂依赖关系的类,Mock 对象让我们在不依赖具体对象的情况下完成测试
mock对象的概念就是我们想要创建一个可以替代实际对象的对象,这个模拟对象要可以通过特定参数调用特定的方法,并且能返回预期结果。
桩指的是用来替换具体功能的程序段。桩程序可以用来模拟已有程序的行为或是对未完成开发程序的一种临时替代。
通过设置预期明确 Mock对象执行时会发生什么:
比如返回特定的值、抛出一个异常、触发一个事件等,又或者调用一定的次数
设置预期和验证预期是同时进行的。设置预期在调用测试类的函数之前完成,验证预期则在它之后。
所以,首先你设定好预期结果,然后去验证你的预期结果是否正确
Mockito 是一个简单的流行的 Mock 框架。它能够帮我们创建 Mock 对象,保持单元测试的独立性。
<dependency>
<groupId>org.mockitogroupId>
<artifactId>mockito-coreartifactId>
<version>3.6.28version>
dependency>
通过方法创建手动注入
class CreateMock {
@Before
public void setup() {
mockUserDao = mock(UserDao.class);
userService = new UserServiceImpl();
userService.setUserDao(mockUserDao);
}
}
通过注解创建并自动注入
class CreateMock {
@Mock
UserDao mockUserDao;
@InjectMocks
private UserServiceImpl userService;
@Before
public void setUp() {
//初始化对象的注解 MockitoAnnotations.initMocks(this);
}
}
import org.junit.Assert;
import org.junit.Test;
import java.util.List;import static org.mockito.Mockito.*;
public class MyTest {
@Test
public void myTest() {
/* 创建 Mock 对象 */
List list = mock(List.class);
/* 设置预期,当调用 get(0) 方法时返回 "111" */
when(list.get(0)).thenReturn("111");
Assert.assertEquals("asd", 1, 1);
/* 设置后返回期望的结果 */
System.out.println(list.get(0));
/* 没有设置则返回 null */
System.out.println(list.get(1));
/* 对 Mock 对象设置无效 */
list.add("12");
list.add("123");
/* 返回之前设置的结果 */
System.out.println(list.get(0));
/* 返回 null */
System.out.println(list.get(1));
/* size 大小为 0 */
System.out.println(list.size());
/* 验证操作,验证 get(0) 调用了 2 次 */
verify(list, times(2)).get(0);
/* 验证返回结果 */
String ret = (String)list.get(0);
Assert.assertEquals(ret, "111");
}
}
Mockito
的底层原理是使用 cglib
动态生成一个 代理类对象,因此,mock
出来的对象其实质就是一个 代理,该代理在 没有配置/指定行为 的情况下,默认返回空值
List mockList = mock(List.class);
@Mock
private List mockList;
@BeforeEach
public void setUp(){
MockitoAnnotations.openMocks(this);
}
@Test
@Order(2)
public void testMockAnnotationField(){
Assertions.assertNotNull(mockList);
}
其中MockitoAnnotations.openMocks(this)
会生成@Mock、@Spy备注的mock对象。
还有对@InjectMocks标注的对象,按类型注入已经生成好的mock对象(注入到成员变量,根据field、setter或构造方法注入)
@Mock
private HelloDao mockHelloDao;
@InjectMocks
private HelloService helloService;
@BeforeEach
public void setUp(){
MockitoAnnotations.openMocks(this);
}
@Test
@Order(3)
public void testGenerateAndInjectMock(){
Assertions.assertNotNull(mockHelloDao);
}
public class HelloService {
private HelloDao helloDao;
public HelloService(HelloDao helloDao){
this.helloDao = helloDao;
}
}
首先会对真实实例进行复制到spy实例,所有的方法调用都会调用到spy实例不会发生在真实实例上。
spy对象除了设置预期行为的方法外,其他方法调用会和原对象的行为一致,达到部分mock的效果。
@Test
public void spyBasedObject(){
List<Integer> realList = new ArrayList<>();
List spyList = spy(realList);
//此时when里的spyList.add(3)不会被预期拦截
when(spyList.add(3)).thenReturn(true);
spyList.add(2);
//会被预期拦截,虽然返回true
boolean addResult1 = spyList.add(3);
System.out.println("addResult:"+addResult1);
//会被预期拦截,虽然返回true
boolean addResult2 = spyList.add(3);
System.out.println("addResult:"+addResult2);
System.out.println("spyList:"+ JSON.toJSONString(spyList));
System.out.println("realList:"+ JSON.toJSONString(realList));
spyList.clear();
System.out.println("spyList:"+ JSON.toJSONString(spyList));
//使用这种语法此时when里的spyList.add(3)可以被预期拦截
doReturn(true).when(spyList).add(3);
System.out.println(JSON.toJSONString(spyList));
spyList.clear();
doCallRealMethod().when(spyList).add(3);
spyList.add(3);
System.out.println("spyList:"+ JSON.toJSONString(spyList));
System.out.println("realList:"+ JSON.toJSONString(realList));
spyList.clear();
when(spyList.size()).thenCallRealMethod();
System.out.println("spyList:"+ JSON.toJSONString(spyList));
System.out.println("realList:"+ JSON.toJSONString(realList));
}
需注意的是:
通过when和andThen的方式设置预期时,when里的调用会真实发生不会用预期拦截。
要避免when里的真实调用,可以使用doReturn这种语法。
/**
* 1.默认mock所有方法不会真实调用mock对象
* 但可以通过thenCallRealMethod和doCallRealMethod来指定哪些方法真实调用
* 2.达到和spy一样局部mock的能力
*/
@Test
@Order(4)
public void useAsSpy(){
HelloDao helloDao = mock(HelloDao.class);
//指定真实调用
when(helloDao.queryId(anyLong())).thenCallRealMethod();
helloDao.deleteAll();
helloDao.queryId(1L);
//指定真实调用
doCallRealMethod().when(helloDao).insert(anyString());
helloDao.insert("abc");
}
预期时必须符合指定方法+指定参数调用,才能返回预期的值
@Test
public void expectReturnSingleCall(){
List mockList = mock(List.class);
when(mockList.get(0)).thenReturn("000");
when(mockList.get(0)).thenReturn("111");
//如果没有命中设置的预期,调用方法不会有任何行为。return对应方法返回值类型的默认值
mockList.add(1);
mockList.add(2);
//只会使用设置的预期返回值
Assertions.assertEquals(mockList.get(0),"111");
//返回多次都生效
Assertions.assertEquals(mockList.get(0),"111");
//未设置预期返回值的return返回类型的默认值
Assertions.assertNull(mockList.get(1));
}
设置预期时可以使用thenReturn和thenThrow交替使用
@Test
@Order(2)
public void expectReturnSequenceCall1(){
List mockList = mock(List.class);
//代码形式1
when(mockList.get(0)).thenReturn("111").thenReturn("222");
Assertions.assertEquals(mockList.get(0),"111");
Assertions.assertEquals(mockList.get(0),"222");
Assertions.assertEquals(mockList.get(0),"222");
}
/**
* 代码形式2: 意义和上面是一样的
*/
@Test
@Order(2)
public void expectReturnSequenceCall2(){
List mockList = mock(List.class);
//代码形式2
when(mockList.get(0)).thenReturn("111","222");
Assertions.assertEquals(mockList.get(0),"111");
Assertions.assertEquals(mockList.get(0),"222");
Assertions.assertEquals(mockList.get(0),"222");
}
/**
* 代码形式3: 意义和上面是一样的
*/
@Test
@Order(2)
public void expectReturnSequenceCall3(){
List mockList = mock(List.class);
//代码形式3
doReturn("111").doReturn("222").when(mockList).get(0);
Assertions.assertEquals(mockList.get(0),"111");
Assertions.assertEquals(mockList.get(0),"222");
Assertions.assertEquals(mockList.get(0),"222");
}
/**
* 使用given和will*系列设置预期行为
*/
@Test
@Order(5)
public void expectReturnUsingGiven(){
List mockList = mock(List.class);
given(mockList.size()).willReturn(1);
Assertions.assertEquals(1,mockList.size());
given(mockList.size()).willThrow(new RuntimeException());
Assertions.assertThrows(RuntimeException.class,() -> mockList.size());
}
/**
* 对于返回值为void的方法需要设置预期
* 1.用doNothing的语法
* 2.用doAnswer的语法,也可以用现成的Answer:
* Answers.RETURNS_DEFAULTS
* Answers.RETURNS_SMART_NULLS
* Answers.RETURNS_MOCKS
* Answers.RETURNS_DEEP_STUBS
* Answers.CALLS_REAL_METHODS
* Answers.RETURNS_SELF
*/
@Test
@Order(6)
public void mockVoidMethod(){
HelloDao mockHelloDao = mock(HelloDao.class);
doAnswer(invocationOnMock -> {
Object[] arguments = invocationOnMock.getArguments();
System.out.println("arguments:"+ JSON.toJSONString(arguments));
return "success";
}).when(mockHelloDao).deleteAll();
//doNothing只能作用于返回值为void的方法
doNothing().when(mockHelloDao).deleteAll();
mockHelloDao.deleteAll();
verify(mockHelloDao).deleteAll();
}
verify校验mock对象的行为,verify失败会抛出对应异常。
也可以结合junit4或junit5的断言一起使用。
/**
* 校验是否调用过1次某个方法(指定方法+指定参数)
* 1. 如果确实调用过1次正常执行
* 2. 如果没有调用过抛出异常WantedButNotInvoked
* 3. 调用超过1次也会异常TooManyActualInvocations(实际调用次数比预期多)
*/
@Test
public void verifyMethodCallSingleTime(){
List mockedList = mock(List.class);
mockedList.add("one");
//调用过add("one")一次,则不会抛出异常
Executable executable = () -> verify(mockedList).add("one");
Assertions.assertDoesNotThrow(executable);
//没有调用过add("one"),会抛出异常
Executable executable2 = () -> verify(mockedList).add("two");
Assertions.assertThrows(WantedButNotInvoked.class,executable2);
mockedList.add("one");
//调用过add("one")两次,会抛出异常
Executable executable3 = () -> verify(mockedList).add("one");
Assertions.assertThrows(TooManyActualInvocations.class,executable3);
}
/**
* 校验是否调用过某个方法指定次数(指定方法+指定参数)
* 1. 如果确实调用过该指定次数正常执行
* 2. 如果没有调用过抛出异常WantedButNotInvoked
* 3. 调用过但次数不一样会异常
* TooManyActualInvocations(实际调用次数比预期多)
* TooFewActualInvocations(实际调用次数比预期少)
*/
@Test
public void verifyMethodCallMultiTimes(){
List mockedList = mock(List.class);
mockedList.add("one");
mockedList.add("one");
//调用过add("one")两次,比预期多会抛出异常
Executable executable1 = () -> verify(mockedList,times(1)).add("one");
Assertions.assertThrows(TooManyActualInvocations.class,executable1);
//调用过add("one")两次,比预期少会抛出异常
Executable executable2 = () -> verify(mockedList,times(3)).add("one");
Assertions.assertThrows(TooFewActualInvocations.class,executable2);
Executable executable3 = () -> verify(mockedList,times(2)).add("one");
Assertions.assertDoesNotThrow(executable3);
}
/**
* 其他关于调用次数的校验
*/
@Test
public void otherTimesVerify(){
List mockedList = mock(List.class);
//预期:从未调用过
verify(mockedList,never()).add(0);
//预期:至少调用过n次
verify(mockedList,atLeast(1)).add(0);
//预期:至少调用过1次,相当于mockedList,atLeast(1)
verify(mockedList,atLeastOnce()).add(0);
//预期:至多调用过n次
verify(mockedList,atMost(2)).add(0);
}
/**
* 校验事件是否按预期的顺序发生
* 1.构造时,将所有的mock对象作为入参
* 2.再按预期顺序调用InOrder的verify方法
*/
@Test
public void testInOrder(){
List mockedList = mock(List.class);
Set set = mock(Set.class);
Collection col = mock(Collection.class);
set.add(1L);
set.add(2L);
mockedList.add("abc");
col.add("bcd");
InOrder inOrder = inOrder(mockedList, set, col);
inOrder.verify(set).add(1L);
inOrder.verify(set).add(2L);
inOrder.verify(mockedList).add("abc");
inOrder.verify(col).add("bcd");
}
/**
* verify失败时会,异常的message通过description指定
*/
@Test
public void testVerifyDescription(){
List mockedList = mock(List.class);
mockedList.add(1);
verify(mockedList,description("没有调用size")).size();
}
/**
* reset: 清除所有的调用记录和预期
*/
@Test
public void testReset(){
List<Integer> mockedList = mock(List.class);
doReturn(100).when(mockedList).size();
System.out.println("size1:"+mockedList.size());
verify(mockedList,description("没有调用size1")).size();
reset(mockedList);
//预期清除返回默认值0,调用记录清零重新记录
System.out.println("size2"+mockedList.size());
verify(mockedList,description("没有调用size2")).size();
}
ArgumentMatchers:用来定义指定特征预的参数范围,而不用每次指定单一具体的参数值。
参数匹配器可以当作任意方法的调用:常用在mock对象直接方法调用、when、verify里的mock方法调用
如果某个方法有多个参数,只要用了参数匹配器,所有参数都需用对应的参数匹配器否则报错。
This exception may occur if matchers are combined with raw values:
//incorrect:
someMethod(anyObject(), “raw String”);
When using matchers, all arguments have to be provided by matchers.
For example:
//correct:
someMethod(anyObject(), eq(“String by matcher”));
/*
* 一般放在: when、mock直接调用、verify里
*/
@Test
@Order(1)
public void usingScene(){
List mockList = mock(List.class);
//表示任意int都返回111
when(mockList.get(anyInt())).thenReturn("111");
Assertions.assertEquals("111",mockList.get(0));
Assertions.assertEquals("111",mockList.get(1));
Assertions.assertEquals("111",mockList.get(anyInt()));
//使用int作为参数调用了3次
verify(mockList,times(3)).get(anyInt());
//也可以放在任意方法
System.out.println(anyInt());
}
/**
* 任意类型的值都满足
*/
@Test
@Order(2)
public void anyPattern(){
//基本类型
System.out.println(anyByte());
System.out.println(anyShort());
System.out.println(anyInt());
System.out.println(anyLong());
System.out.println(anyFloat());
System.out.println(anyDouble());
System.out.println(anyBoolean());
System.out.println(anyString());
System.out.println(anyChar());
//任意类型,可以使用范型
System.out.println((String)any());
//任意类型
System.out.println(any(BigDecimal.class));
//集合
System.out.println(anyList());
System.out.println(anySet());
System.out.println(anyCollection());
System.out.println(anyMap());
}
@Test
public void strPattern(){
//包含子串
contains("abc");
//满足正则的字符串
matches("\\d+");
startsWith("abc");
endsWith("abc");
}
/**
* 自定义参数匹配:只要实现ArgumentMatcher即可
* public interface ArgumentMatcher {
* boolean matches(T var1);
* }
*/
@Test
public void customPattern(){
ArgumentMatcher<Integer> intMatcher = i -> i > 0;
intThat(intMatcher);
byteThat(null);
shortThat(null);
longThat(null);
charThat(null);
booleanThat(null);
floatThat(null);
doubleThat(null);
//任意类型,范型
argThat(null);
}
对mock对象方法调用时参数进行捕捉:ArgumentCaptor
/**
* 捕捉入参类型是String的入参
*/
@Captor
private ArgumentCaptor<String> captor;
/**
* 捕捉入参类型是Integer的入参
*/
@Captor
private ArgumentCaptor<Integer> captorInt1;
/**
* 捕捉入参类型是Integer的入参
*/
@Captor
private ArgumentCaptor<Integer> captorInt2;
@BeforeEach
public void setUp(){
MockitoAnnotations.initMocks(this);
}
/**
* 单参数类型的方法调用
* 1.设置入参类型的ArgumentCaptor
* 2.verify时调用将capture()作为调用方法的入参来捕捉该方法调用的入参
* 3.通过getValue()来获取最后捕捉的参数
* 4.getAllValues()来获取历史捕捉的参数
*/
@Test
public void testSingleParamCaptor(){
List<String> mockList = mock(List.class);
mockList.add("java");
mockList.add("js");
mockList.add("c++");
//When verify,you can capture the arguments of the calling method
verify(mockList,times(3)).add(captor.capture());
System.out.println(JSON.toJSONString(captor.getAllValues()));
System.out.println(captor.getValue());
System.out.println(captor.getValue());
System.out.println(captor.getValue());
}
/**
* 用同一个参数捕捉器不同的方法调用,发现:
* 参数记录的顺序是按verify的顺序记录的,跟实际调用顺序无关
*/
@Test
public void testMultiParamUseSameCapture(){
List<String> mockList = mock(List.class);
mockList.add("java");
mockList.remove("js");
mockList.add("c++");
//When verify,you can capture the arguments of the calling method
verify(mockList,times(2)).add(captor.capture());
verify(mockList).remove(captor.capture());
System.out.println(JSON.toJSONString(captor.getAllValues()));
System.out.println(captor.getValue());
System.out.println(captor.getValue());
System.out.println(captor.getValue());
}
/**
* 多参数同一次方法调用,用不同的记录器记录不同的参数:
* 参数记录的顺序是按verify的顺序记录的,跟实际调用顺序无关
*/
@Test
public void testMultiParamUseDiffCapture(){
List<String> mockList = mock(List.class);
mockList.subList(1,2);
mockList.subList(3,4);
//When verify,you can capture the arguments of the calling method
verify(mockList,times(2)).subList(captorInt1.capture(),captorInt2.capture());
System.out.println(JSON.toJSONString(captorInt1.getAllValues()));
System.out.println(JSON.toJSONString(captorInt2.getAllValues()));
System.out.println(captorInt1.getValue());
System.out.println(captorInt2.getValue());
}
/**
* * mock某个类的静态方法调用
*/
@Test
public void testMockStatic(){
MockedStatic<MyStaticClass> myStaticClassMockedStatic = Mockito.mockStatic(MyStaticClass.class);
Mockito.when(MyStaticClass.getKey()).thenReturn("cba");
Assertions.assertEquals(MyStaticClass.getKey(),"cba");
//mock返回值或异常
Mockito.when(MyStaticClass.getKey2()).thenThrow(new RuntimeException());
Assertions.assertThrows(RuntimeException.class,() -> MyStaticClass.getKey2());
//关掉static方法就会走真实逻辑
myStaticClassMockedStatic.close();
Assertions.assertEquals(MyStaticClass.getKey(),"abc");
Assertions.assertEquals(MyStaticClass.getKey2(),"abd");
}
引入mockito-core包
加上@RunWith(MockitoJUnitRunner.class),可以自动处理@Mock,@Spy,@InjectMocks等注解
@RunWith(MockitoJUnitRunner.class)
public class Demo1_MockitoWithJunit4 {
@Mock
public List mockList;
@InjectMocks
public MyMockInject myMockInject;
/**
* Mock注解注入
*/
@Test
public void baseMock(){
Assert.assertNotNull(mockList);
}
/**
* InjectMocks注解
*/
@Test
public void baseInject(){
Assert.assertNotNull(myMockInject);
int mockSize = myMockInject.size();
Assert.assertEquals(0,mockSize);
when(mockList.size()).thenReturn(2);
int expectSize = myMockInject.size();
myMockInject.size();
Assert.assertEquals(2,expectSize);
}
public static class MyMockInject{
private List list;
public MyMockInject(List list){
this.list = list;
}
public int size(){
System.out.println("call size");
return list.size();
}
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {AppConfig.class})
public abstract class AbstractTestCase {}
public class HelloControllerTest extends AbstractTestCase {
/**
* 声明mock对象
*/
@Mock
private HelloService mockHelloService;
@Autowired
private HelloController helloController;
/**
* 生成mock对象,并手动注入到被依赖对象
*/
@Before
public void setUp(){
MockitoAnnotations.openMocks(this);
helloController.setHelloService(mockHelloService);
}
@Test
public void testHello(){
helloController.hello();
}
}
@RunWith(SpringRunner.class)
@SpringBootTest(classes = AppBootStrap.class)
public class AbstractTestCase {}
/**
* 1。使用@MockBean注解的的对象,会生成一个Mock的bean.不会生成原来的bean
* 2。并会将该bean注入到依赖该bean的其他bean中
* 3。正常的bean还是会正常组装注入
*/
public class HelloControllerMockBeanTest extends AbstractTestCase {
@Autowired
private HelloController helloController;
@MockBean
private HelloService helloService;
@Test
public void testHello(){
helloController.hello();
}
}
/**
* 1。使用@SpyBean注解的的对象,会复制生成一个一样的的bean.默认行为与原对象行为一致
* 2。并会将该bean注入到依赖该bean的其他bean中
* 3。正常的bean还是会正常组装注入
*/
public class HelloControllerMockSpyTest extends AbstractTestCase {
@Autowired
private HelloController helloController;
@SpyBean
private HelloService helloService;
@Test
public void testHello(){
System.out.println("============test1================");
helloController.hello();
System.out.println("============test1================");
System.out.println("============test2================");
Mockito.doNothing().when(helloService).sayHello();
helloService.sayHello();
System.out.println("============test2================");
}
}
@ExtendWith(MockitoExtension.class)
public class Demo1_MockitoWithJunit5 {
@Mock
public List mockList;
@InjectMocks
public MyMockInject myMockInject;
/**
* Mock注解注入
*/
@Test
public void baseMock(){
Assertions.assertNotNull(mockList);
}
/**
* InjectMocks注解
*/
@Test
public void baseInject(){
Assertions.assertNotNull(myMockInject);
int mockSize = myMockInject.size();
Assertions.assertEquals(0,mockSize);
when(mockList.size()).thenReturn(2);
int expectSize = myMockInject.size();
myMockInject.size();
Assertions.assertEquals(2,expectSize);
}
public static class MyMockInject{
private List list;
public MyMockInject(List list){
this.list = list;
}
public int size(){
System.out.println("call size");
return list.size();
}
}
}
@ExtendWith({SpringExtension.class, MockitoExtension.class})
@ContextConfiguration(classes = AppConfig.class)
public class AbstractTestCase {}
public class HelloControllerOnlyJunit5Test extends AbstractTestCase {
@Autowired
private HelloController helloController;
@Mock
private HelloService mockHelloService;
@BeforeEach
public void setUp(){
helloController.setHelloService(mockHelloService);
}
@Test
public void testHello(){
helloController.hello();
}
}
@SpringBootTest(classes = AppBootStrap.class)
public class AbstractTestCase {}
/**
* 1。使用@MockBean注解的的对象,会生成一个Mock的bean.不会生成原来的bean
* 2。并会将该bean注入到依赖该bean的其他bean中
* 3。正常的bean还是会正常组装注入
*/
public class HelloControllerMockBeanTest extends AbstractTestCase {
@Autowired
private HelloController helloController;
@MockBean
private HelloService helloService;
@Test
public void testHello(){
System.out.println("============only junit5================");
helloController.hello();
System.out.println("============only junit5================");
}
}
/**
* 1。使用@MockBean注解的的对象,会生成一个spy的bean行为与原类型一致.不会生成原来的bean
* 2。并会将该bean注入到依赖该bean的其他bean中
* 3。正常的bean还是会正常组装注入
*/
public class HelloControllerSpyBeanTest extends AbstractTestCase {
@Autowired
private HelloController helloController;
@SpyBean
private HelloService helloService;
@Test
public void testHello(){
System.out.println("============only junit5================");
helloController.hello();
System.out.println("============only junit5================");
}
}