Mock的使用
文章目录
Mock 常用于单元测试,用来模拟测试代码所依赖的外部类。因为我们所编写的代码往往存在大量复杂的外部依赖,或者依赖于特定的环境才能正常运行。这时我们可以选择使用 Mock 来构造这些外部依赖,使它们产生符合我们期待的行为(例如返回特定的值),但不需要真正地去构造外部依赖。需要注意的是,Mock 出来的对象并不会真实执行。
Java 中常用的 Mock 框架为 Mockito 和 Powermock,Powermock 主要在 Mockito 上添加了 final、private、static 方法的支持,两者可以结合使用。
添加依赖
环境为 Java1.6、Junit4.4
添加 PowerMockito 的依赖,因为使用 PowerMockito 就不需要再导入 Mockito 的依赖了。
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>2.0.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>2.0.2</version>
<scope>test</scope>
</dependency>
注:如果是 Junit4.4 但是导入了powermock-module-junit4-legacy
,在使用@RunWith(PowerMockRunner.class)
时,会报org/junit/internal/runners/TestClassRunnerorg/junit/internal/runners/TestClassRunner
的错。
详情:Mockito 2 Maven · powermock/powermock Wiki · GitHub
使用 Mock
有两种方式来 mock 一个类。
import static org.mockito.Mockito.*;
//1使用方法
PersonDao dao=mock(PersonDao.class);
//2使用注解
@Mock
PersonDao dao;
下面使用到的代码为:
被测试的代码:
public class PersonService {
PersonDao personDao=new PersonDao();
private static int num=3;
private int num2=3;
public void test(){
String str=personDao.test("1");
System.out.println(str);
}
public void testStatic(){
String str=PersonDao.testStatic("1");
System.out.println(str);
}
public void testSingle(){
SingleInstance instance=SingleInstance.getInstance();
String str=instance.test();
System.out.println(str);
}
public void testVoid(){
personDao.testVoid();
}
public int testVerify(){
return 1;
}
}
依赖代码:
public class PersonDao {
public String test(String str){
return "test:"+str;
}
public static String testStatic(String str){
return "test:"+str;
}
public void testVoid(){}
}
mock 普通方法
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class PersonServiceTest {
@Mock
private PersonDao personDao;
@Test
public void run(){
when(personDao.test(anyString())).thenReturn("not find");
System.out.println(personDao.test("1"));
}
}
//输出: not find
Mockito 提供了很多方法来匹配参数,例如 anyString()
、anyInt()
、any()
,而不必知道具体参数。也可以使用 eq()
方法来匹配具体的参数,例如:*when*(personDao.find(*eq*("1"))).thenReturn("not find");
when()...thenReturn()
的过程被称为打桩(stub)。当执行 personDao.find()
方法时,返回所指定的值。
需要注意打桩时,匹配参数要不全用具体值,要不全用Matchers提供,否则会报错:
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers!
Spy
mock 出来的类不会真正执行,但是 Spy 出来的方法会真正执行,但是仍然可以打桩。
@RunWith(MockitoJUnitRunner.class)
public class PersonServiceTest {
@Spy
private PersonDao personDao;
@Test
public void run(){
System.out.println(personDao.test("1"));
}
}
//输出 test:1
mock 出来的方法:
@RunWith(MockitoJUnitRunner.class)
public class PersonServiceTest {
@Mock
private PersonDao personDao;
@Test
public void run(){
System.out.println(personDao.test("1"));
}
}
//输出:null
mock 静态方法
mock 静态方法需要 PowerMock。
import static org.powermock.api.mockito.PowerMockito.*;
@PrepareForTest({PersonDao.class})
@RunWith(PowerMockRunner.class)
public class PersonServiceTest {
@Test
public void run(){
//mock一个静态类,被测试代码依赖的类
mockStatic(PersonDao.class);
//当调用PersonDao.findStatic()方法,返回特定的值
when(PersonDao.testStatic(Mockito.anyString())).thenReturn("not find");
//被测试的类
PersonService personService=new PersonService();
personService.testStatic();
}
}
私有属性的访问与修改
主要是使用到了 Whitebox 的 setInternalState()
和 getInternalState()
方法,能访问、修改私有实例方法和私有静态方法。
私有实例方法,参数传入实例变量:
import static org.powermock.api.mockito.PowerMockito.*;
@RunWith(PowerMockRunner.class)
public class PersonServiceTest {
@Test
public void run(){
PersonService personService=new PersonService();
Whitebox.setInternalState(personService,"num2",5);
int num=Whitebox.getInternalState(personService,"num2");
System.out.println(num);
}
}
私有静态方法,参数传入类:
import static org.powermock.api.mockito.PowerMockito.*;
@RunWith(PowerMockRunner.class)
public class PersonServiceTest {
@Test
public void run(){
//修改
Whitebox.setInternalState(PersonService.class,"num",5);
//访问
int num=Whitebox.getInternalState(PersonService.class,"num");
System.out.println(num);
}
}
Whitebox 提供了很多 API,可以查看https://www.javadoc.io/doc/org.powermock/powermock-reflect/1.6.4/org/powermock/reflect/Whitebox.html
mock 单例模式
@PrepareForTest({SingleInstance.class})
@RunWith(PowerMockRunner.class)
public class PersonServiceTest {
@Mock
private SingleInstance singleInstance;
@Test
public void runSingle(){
mockStatic(SingleInstance.class);
// 获取单例模式的实例方法
when(SingleInstance.getInstance()).thenReturn(singleInstance);
when(singleInstance.test()).thenReturn("not find");
PersonService personService=new PersonService();
personService.testSingle();
}
}
mock void方法
import static org.powermock.api.mockito.PowerMockito.*;
@RunWith(PowerMockRunner.class)
public class PersonServiceTest {
@Mock
private PersonDao personDao;
@Test
public void runVoid(){
PersonService personService=new PersonService();
doNothing().when(personDao).testVoid();
personService.testVoid();
}
}
capture
capture 用来捕获被调用时传入方法的参数值,该方法需要时被 mock 的方法。
import static org.powermock.api.mockito.PowerMockito.*;
@RunWith(PowerMockRunner.class)
public class PersonServiceTest {
@Mock
private PersonDao personDao;
//声明captor类型
@Captor
private ArgumentCaptor<String> captor;
@Test
public void runCapture(){
PersonService personService=new PersonService();
//将mock出来的对象注入PersonService实例
Whitebox.setInternalState(personService,"personDao",personDao);
//先执行方法
personService.test();
//使用captor捕获变量
Mockito.verify(personDao).test(captor.capture());
//打印变量
System.out.println(captor.getValue());
}
}
验证
执行方法之后,需要验证方法执行是否符合预期结果,一般使用 Assert.assertEquals()
来验证*。*
import static org.powermock.api.mockito.PowerMockito.*;
@RunWith(PowerMockRunner.class)
public class PersonServiceTest {
@Mock
private PersonDao personDao;
@Test
public void runVerify(){
PersonService personService=new PersonService();
int str=personService.testVerify();
//如果结果不为1,则运行失败
Assert.assertEquals(1,str);
}
}
总结
mock 能帮助我们解决很多依赖构建问题,同时提供的大量 API 能帮助我们方便测试极端情况的发生。但是随着测试用例不断增长,测试类也会越来越复杂,我们在编写时也要注意测试用例的维护。
其他
-
如何 mock 时,报错
Could not initialize plugin: interface org.mockito.plugins.MockMaker (alternate: null)
,需要引入依赖<dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy</artifactId> <version>1.12.10</version> </dependency>
-
mock 要在执行真实方法之前,不然会报 null,而 verify 验证跟 captor 捕获一般在真实方法调用后执行。
-
当方法的两个参数类型相同时,也可能报错:
org.mockito.exceptions.misusing.InvalidUseOfMatchersException: Invalid use of argument matchers! 2 matchers expected, 1 recorded:
解决办法为:都换成具体参数。
文章作者 梧桐碎梦
上次更新 2022-07-02 22:33:53