当前位置:网站首页>Mockito单元测试
Mockito单元测试
2022-08-04 03:00:00 【清风拂来水波不兴】
Mockito
mockito可以模拟一个真实的对象,如模拟需要连接数据库的对象或者是需要启动spring容器的对象,它并不真正的连接数据库,所以测试时间更快。该模拟出来的对象调用的方法默认情况都是null、0、false。
使用方式:
1.在测试类上加上@RunWith(MockitoJUnitRunner.class)注解,如果是springboot应用用@ExtendWith(MockitoExtension.class)注解。
2.模拟要测试的对象,有多种方式
- 使用mock方法,如ArrayList list = mock(ArrayList.class);
- 对象上加@Mock注解,并使用MockitoAnnotations.openMocks(this);注册,这个方法需要在运行测试函数之前被调用,一般放在@Before中
- @MockBean
验证verify
@DisplayName("MockitoDemo测试类")
@ExtendWith(MockitoExtension.class)
public class MockitoDemo {
ArrayList list;
@Test
public void mockList() {
list = mock(ArrayList.class); //mock该对象
list.get(0); //此时任何方法返回值都为null
verify(list).get(0); //验证该对象方法是否被执行过。此处执行过,成功
}
}
verify()方法是验证该mock对象是否执行过某些方法,如果前面的代码中没有执行过,那会报错,并在控制台上输出 Wanted but not invoked错误。
和断言是一样的。
打桩Stub
让mock对象的方法返回某些特定的值,由自己diy,通过when和then设置。
@Test
public void mockList() {
list = mock(ArrayList.class);
//当get(0)时返回hello
when(list.get(0)).thenReturn("hello");
//get(1)时方法抛出异常
when(list.get(1)).thenThrow(new RuntimeException("get any"));
System.out.println(list.get(0)); //hello
System.out.println(list.get(1)); //java.lang.RuntimeException: get any
}
默认情况下,没有打桩的函数和函数参数组合返回值没有意义,都是返回null类似的值
通过mock(Foo.class, new YourOwnAnswer()); 可以改变默认情况,没有打桩的程序自己决定返回值
打桩可以被复写(最后的才是最终结果),但是最好不要
另一种方式:
doReturn(1).when(list).get(0);
连续打桩
list = mock(ArrayList.class);
when(list.get(0)).thenReturn(1).thenReturn(2);
//等同于 when(list.get(0)).thenReturn(1,2);
System.out.println(list.get(0)); //1
System.out.println(list.get(0)); //2
System.out.println(list.get(0)); //后续的任然是2
注意:如果多条打桩动作不在一条语句,那么就是覆盖
回调方式打桩
list = mock(ArrayList.class);
when(list.get(0)).thenAnswer(new Answer<String>() {
@Override
public String answer(InvocationOnMock invocation) throws Throwable {
Object[] arguments = invocation.getArguments();
Method method = invocation.getMethod();
//invocation.callRealMethod();
Object mock = invocation.getMock();
return "diy 调用方法";
}
});
list.get(0);//diy 调用方法
- 通过thenAnswer方法,参数是自己实现的Answer类,这种方式和Proxy动态代理使用方式类似
另外还有:doNothing()、doCallRealMethod()
参数匹配器matchers
@ExtendWith(MockitoExtension.class)
public class MockitoDemo {
ArrayList list;
@Test
public void mockList() {
list = mock(ArrayList.class);
//可以使用内置的any()、anyInt()等匹配器,代表所有值、所有整数
when(list.get(anyInt())).thenReturn("get method");
//可以使用自定义的参数匹配器
when(list.contains(argThat(new MyMatcher()))).thenReturn(true);
System.out.println(list.get(999)); //get method
System.out.println(list.contains(998)); //true
System.out.println(list.contains(999)); //false
//验证是否调用过get函数。这里的anyInt()就是一个参数匹配器。
verify(list).get(anyInt());
}
}
//自定义参数匹配器类
class MyMatcher implements ArgumentMatcher<Integer> {
@Override
public boolean matches(Integer v) {
//表示所有的偶数
return v % 2 == 0;
}
}
注意:如果你使用了参数匹配器, 那么所有参数都要用匹配器。
verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
// 上述代码是正确的,因为eq()也是一个参数匹配器
验证续
验证次数
verify()方法实则需要指定对应的方法被执行了多少次,默认是1次。
list = mock(ArrayList.class);
list.add(1);
list.add(2);list.add(2);
list.add(3);list.add(3);list.add(3);
verify(list,times(1)).add(1); //等于verify(list).add(1)
verify(list,times(2)).add(2); //表示add(2)需要执行正好2次
verify(list,times(3)).add(3);
verify(list,atLeast(2)).add(3); //最少执行2次
verify(list,atMost(2)).add(1); //最多执行2次
verify(list,never()).add(999); //违背执行过
验证执行顺序
用于限制mock对象的执行顺序
list = mock(ArrayList.class);
list.get(0);
list.get(1);
//1.为该mock对象创建inorder验证器
InOrder inOrder = inOrder(list);
//2.以你想要的顺序执行验证,顺序一致就验证成功
inOrder.verify(list).get(0);
inOrder.verify(list).get(1);
//Verification in order failure ,验证失败
//inOrder.verify(list).get(1);
//inOrder.verify(list).get(0);
上例是单个对象方法的执行顺序,也可以限制多个mock对象:
list1 = mock(ArrayList.class);
list2 = mock(ArrayList.class);
list1.get(0);
list2.get(1);
//1.为这两个mock对象创建inorder验证器
InOrder inOrder = inOrder(list1, list2);
//2.验证通过
inOrder.verify(list1).get(0);
inOrder.verify(list2).get(1);
带超时的验证
验证的超时时间,验证时间太长了就立即失败
verify(mock, timeout(500).times(1)).method();
spy监控
用于创建spy间谍,去监控真实对象,使用spy对象时会调用真实的方法,但是也可以给spy对象的方法打桩。其实就是真实对象和mock对象的中间形式。
LinkedList<String> linkedList = new LinkedList<>();
LinkedList<String> spy = spy(linkedList); //创建spy对象
doReturn(100).when(spy).size(); //给某些方法打桩
spy.add("one");
spy.add("two");
System.out.println(spy); //[one, two]
System.out.println(spy.size()); //100
verify(spy).add("one"); //验证通过
- 注意真实对象还是不变的,变的只是spy对象。
- 和@Mock类似,还可以用@Spy注解。
ArgumentCaptor
前面的验证大部分是验证方法调用这一过程,如果要验证方法调用时使用的参数就需要用ArgumentCaptor。
ArrayList<String> list = mock(ArrayList.class);
//mock对象调用两次add方法,注意参数
list.add("zhangsan");
list.add("lisi");
//创建ArgumentCaptor对象,String泛型是需要捕捉的方法的参数类型
ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class);
//调用verify并且捕捉list.add调用的所有参数。需要注意防止验证失败
verify(list,atLeast(0)).add(argumentCaptor.capture());
//断言,argumentCaptor.getValue()返回最后一次方法调用的参数
Assertions.assertEquals("lisi", argumentCaptor.getValue());
//argumentCaptor.getAllValues()返回所有参数值
Assertions.assertArrayEquals(new Object[]{"zhangsan","lisi"},
argumentCaptor.getAllValues().toArray());
使用上面的自定义参数匹配器Argument Matcher也可以实现参数的验证,使用ArgumentCaptor在以下的情况下更合适 :
- 自定义不能被重用的参数匹配器
- 你仅需要断言参数值
打桩时用自定义参数匹配器更好。
行为驱动开发
采用given->when->that的顺序编写单元测试
自动实例化
@InjectMocks注解和@Mock配合使用,用于有依赖关系的模拟对象
@Data
public class UserInfo {
private String name;
private String password;
public UserInfo(String name, String password) {
this.name = name;
this.password = password;
}
}
@Service
public class UserInfoService {
@Autowired
private UserInfoDao userInfoDao;
public void printInfo() {
UserInfo userInfo = userInfoDao.select();
System.out.println(userInfo);
}
}
public interface UserInfoDao {
UserInfo select();
}
如果我要测试这个service,并且不想和数据库有交互,那么可以创建一个UserInfoDao mock对象。被测试类标注为@InjectMocks时,会自动实例化,并且把@Mock或者@Spy标注过的依赖注入进去。
@ExtendWith(MockitoExtension.class)
public class UserInfoServiceTest {
@InjectMocks
private UserInfoService userInfoService;
@Mock
private UserInfoDao userInfoDao;
@Test
public void testPrint() {
UserInfo userInfo = new UserInfo("admin", "123");
when(userInfoDao.select()).thenReturn(userInfo);
userInfoService.printInfo();
}
}
springboot测试
1.controller
mockito提供了MockMvc类,用于模拟web环境,发送请求到controller。
MockMvcBuilders.* 初始化web环境,有两种初始化方式
- MockMvcBuilders.standaloneSetup(hrjtCcsDealController).build() 针对单个controller进行构造环境
- MockMvcBuilders.webAppContextSetup(webAppContext).build() 针对整个web环境进行构造
- MockMvcRequestBuilders.* 构造请求:有get、post……
- MockMvcResultMatchers.*:搭配ResultActions接口的andExpect()方法来匹配期望的结果。MockMvcResultMatchers.status()方法返回常用的结果判断
- MockMvcResultHandlers.*:搭配ResultActions接口的andDo()方法对结果进行处理。MockMvcResultHandlers.print()方法返回常用的结果判断
注意:目前单个类容器还无法识别swagger对参数的校验注解。
@DisplayName("StudentController的测试")
@LunaFrameworkTest(classes = {BizTestConfig.class})
public class StudentControllerTest {
public MockMvc mockMvc;
@InjectMocks
public StudentController studentController;
@Mock
public StudentService studentService;
@Before
public void before() {
MockitoAnnotations.openMocks(this);
//构建MockMvc
mockMvc = MockMvcBuilders.standaloneSetup(studentController).build();
}
@Test
public void t1() throws Exception {
StudentPO studentPO = getStudent();
//任何情况都返回0
Mockito.when(studentService.create(any())).thenReturn(0);
//覆盖上一条语句,如果pojo校验正确,返回1
Mockito.when(studentService.create(argThat(new StudentVerfiy()))).thenReturn(1);
//发送create请求
mockMvc.perform(MockMvcRequestBuilders.post("/student/create")
//设置request请求的内容
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(asJson(studentPO))
.accept(MediaType.APPLICATION_JSON_UTF8)
)
//校验response是否为200,插入成功才是200
.andExpect(status().isOk())
//.andExpect(content().json("xxx"))
//打印
.andDo(print());
}
public StudentPO getStudent() {
StudentPO studentPO = new StudentPO();
studentPO.setUrid(UuidUtils.generateUuid());
studentPO.setAge(20);
studentPO.setName("马大哈");
studentPO.setClassid("2");
studentPO.setSex("男");
studentPO.setTenantid(20L);
studentPO.setHeight(169.2f);
return studentPO;
}
public String asJson(Object o) {
ObjectMapper objectMapper = new ObjectMapper();
String s;
try {
s = objectMapper.writeValueAsString(o);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
return s;
}
}
//自定义的校验器
class StudentVerfiy implements ArgumentMatcher<StudentPO> {
@Override
public boolean matches(StudentPO argument) {
//写校验逻辑
if (argument.getAge() > 150 || argument.getAge() < 0)
return false;
if (!argument.getSex().equals("男") && !argument.getSex().equals("女"))
return false;
return true;
}
}
- .perform() : 执行一个MockMvcRequestBuilders的请求;MockMvcRequestBuilders有.get()、.post()、.put()、.delete()等请求。
- .andDo() : 添加一个MockMvcResultHandlers结果处理器,可以用于打印结果输出(MockMvcResultHandlers.print())。
- .andExpect : 添加MockMvcResultMatchers验证规则,验证执行结果是否正确。
2.service层
采用懒加载类的方式,只针对当前使用的service进行bean注入。当前service中涉及到的DaoManager和Client都要mock。
@DisplayName("StudentService的测试")
@LunaFrameworkTest(classes = {BizTestConfig.class, AbstractDaoTestConfig.class})
public class StudentServiceTest {
@InjectMocks //接口需要手动初始化
private StudentService studentService = new StudentServiceImpl();
@Mock
private StudentMapper studentMapper;
@Test
public void t1() {
Mockito.when(studentMapper.insert(any())).thenReturn(0);
Mockito.when(studentMapper.insert(argThat(new StudentVerfiy()))).thenReturn(1);
int res = studentService.create(getStudent());
Assertions.assertEquals(1, res);
}
@Before
public void start() {
MockitoAnnotations.openMocks(this);
}
}
补充:@[email protected]获取数据
中文文档:
边栏推荐
- Oracle迁移到瀚高之后,空值问题处理
- Rongyun "Audio and Video Architecture Practice" technical session [complete PPT included]
- 验证码业务逻辑漏洞
- 安装postgis时报找不到“POSTGIS_VERSION”这个函数
- What is SVN (Subversion)?
- Exclude_reserved_words 排除关键字
- 为什么用Selenium做自动化测试
- Returns the maximum number of palindromes in a string
- C program compilation and predefined detailed explanation
- 三分建设,七分管理!产品、系统、组织三管齐下节能降耗
猜你喜欢
Priority_queue element as a pointer, the overloaded operators
融云「音视频架构实践」技术专场【内含完整PPT】
Development of Taurus. MVC WebAPI introductory tutorial 1: download environment configuration and operation framework (including series directory).
【Playwright测试教程】5分钟上手
Homemade bluetooth mobile app to control stm8/stm32/C51 onboard LED
C program compilation and predefined detailed explanation
Zabbix set up email alert + enterprise WeChat alert
How to drop all tables under database in MySQL
怎样提高网络数据安全性
LeetCode每日一题(2285. Maximum Total Importance of Roads)
随机推荐
View mysql deadlock syntax
Presto中broadcast join和partition join执行计划的处理过程
Engineering drawing review questions (with answers)
2022.8.3-----leetcode.899
unsafe.Pointer, pointer, reference in golang
ingress 待完善
查看mysql死锁语法
如何在MySQL中的数据库下删除所有的表
golang中的unsafe.Pointer,指针,引用
数据安全峰会2022 | 美创DSM获颁“数据安全产品能力验证计划”评测证书
Pine脚本 | 如何显示和排版绘图开关?
架构实战营模块三作业
【指针内功修炼】深度剖析指针笔试题(三)
LeetCode每日一题(2285. Maximum Total Importance of Roads)
sqoop ETL tool
三分建设,七分管理!产品、系统、组织三管齐下节能降耗
小程序:扫码打开参数解析
QNX Hypervisor] 10.2 vdev 8259 2.2 user manual
瑞能微计量芯片RN2026的实用程序
织梦内核电动伸缩门卷闸门门业公司网站模板 带手机版【站长亲测】